@blackcode_sa/metaestetics-api 1.12.43 → 1.12.46

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.
package/dist/index.mjs CHANGED
@@ -1445,14 +1445,14 @@ function calculateItemSubtotal(item) {
1445
1445
  if (item.type === "note") {
1446
1446
  return 0;
1447
1447
  }
1448
+ const quantity = item.quantity || 0;
1448
1449
  if (item.priceOverrideAmount !== void 0 && item.priceOverrideAmount !== null) {
1449
- return item.priceOverrideAmount;
1450
+ return item.priceOverrideAmount * quantity;
1450
1451
  }
1451
1452
  const price = item.price || 0;
1452
- const quantity = item.quantity || 0;
1453
1453
  return price * quantity;
1454
1454
  }
1455
- function calculateFinalBilling(zonesData, taxRate = 0.2) {
1455
+ function calculateFinalBilling(zonesData, taxRate = 0.081) {
1456
1456
  let subtotalAll = 0;
1457
1457
  Object.values(zonesData).forEach((items) => {
1458
1458
  items.forEach((item) => {
@@ -1516,7 +1516,7 @@ async function addItemToZoneUtil(db, appointmentId, zoneId, item) {
1516
1516
  updatedAt: now
1517
1517
  };
1518
1518
  zonesData[zoneId].push(itemWithSubtotal);
1519
- const finalbilling = calculateFinalBilling(zonesData);
1519
+ const finalbilling = calculateFinalBilling(zonesData, 0.081);
1520
1520
  const appointmentRef = doc3(db, APPOINTMENTS_COLLECTION, appointmentId);
1521
1521
  await updateDoc3(appointmentRef, {
1522
1522
  "metadata.zonesData": zonesData,
@@ -1540,7 +1540,7 @@ async function removeItemFromZoneUtil(db, appointmentId, zoneId, itemIndex) {
1540
1540
  if (items.length === 0) {
1541
1541
  delete metadata.zonesData[zoneId];
1542
1542
  }
1543
- const finalbilling = calculateFinalBilling(metadata.zonesData);
1543
+ const finalbilling = calculateFinalBilling(metadata.zonesData, 0.081);
1544
1544
  const appointmentRef = doc3(db, APPOINTMENTS_COLLECTION, appointmentId);
1545
1545
  await updateDoc3(appointmentRef, {
1546
1546
  "metadata.zonesData": metadata.zonesData,
@@ -1565,8 +1565,19 @@ async function updateZoneItemUtil(db, appointmentId, zoneId, itemIndex, updates)
1565
1565
  ...updates,
1566
1566
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1567
1567
  };
1568
+ console.log(`[updateZoneItemUtil] BEFORE recalculation:`, {
1569
+ itemIndex,
1570
+ quantity: items[itemIndex].quantity,
1571
+ priceOverrideAmount: items[itemIndex].priceOverrideAmount,
1572
+ price: items[itemIndex].price,
1573
+ oldSubtotal: items[itemIndex].subtotal
1574
+ });
1568
1575
  items[itemIndex].subtotal = calculateItemSubtotal(items[itemIndex]);
1569
- const finalbilling = calculateFinalBilling(metadata.zonesData);
1576
+ console.log(`[updateZoneItemUtil] AFTER recalculation:`, {
1577
+ itemIndex,
1578
+ newSubtotal: items[itemIndex].subtotal
1579
+ });
1580
+ const finalbilling = calculateFinalBilling(metadata.zonesData, 0.081);
1570
1581
  const appointmentRef = doc3(db, APPOINTMENTS_COLLECTION, appointmentId);
1571
1582
  await updateDoc3(appointmentRef, {
1572
1583
  "metadata.zonesData": metadata.zonesData,
@@ -18509,73 +18520,6 @@ var BrandService = class extends BaseService {
18509
18520
  ...docSnap.data()
18510
18521
  };
18511
18522
  }
18512
- /**
18513
- * Exports brands to CSV string, suitable for Excel/Sheets.
18514
- * Includes headers and optional UTF-8 BOM.
18515
- * By default exports only active brands (set includeInactive to true to export all).
18516
- */
18517
- async exportToCsv(options) {
18518
- var _a, _b;
18519
- const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
18520
- const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
18521
- const headers = [
18522
- "id",
18523
- "name",
18524
- "manufacturer",
18525
- "website",
18526
- "description",
18527
- "isActive"
18528
- ];
18529
- const rows = [];
18530
- rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
18531
- const PAGE_SIZE = 1e3;
18532
- let cursor;
18533
- const baseConstraints = [];
18534
- if (!includeInactive) {
18535
- baseConstraints.push(where33("isActive", "==", true));
18536
- }
18537
- baseConstraints.push(orderBy19("name_lowercase"));
18538
- while (true) {
18539
- const constraints = [...baseConstraints, limit17(PAGE_SIZE)];
18540
- if (cursor) constraints.push(startAfter15(cursor));
18541
- const q = query33(this.getBrandsRef(), ...constraints);
18542
- const snapshot = await getDocs33(q);
18543
- if (snapshot.empty) break;
18544
- for (const d of snapshot.docs) {
18545
- const brand = { id: d.id, ...d.data() };
18546
- rows.push(this.brandToCsvRow(brand));
18547
- }
18548
- cursor = snapshot.docs[snapshot.docs.length - 1];
18549
- if (snapshot.size < PAGE_SIZE) break;
18550
- }
18551
- const csvBody = rows.join("\r\n");
18552
- return includeBom ? "\uFEFF" + csvBody : csvBody;
18553
- }
18554
- brandToCsvRow(brand) {
18555
- var _a, _b, _c, _d, _e, _f;
18556
- const values = [
18557
- (_a = brand.id) != null ? _a : "",
18558
- (_b = brand.name) != null ? _b : "",
18559
- (_c = brand.manufacturer) != null ? _c : "",
18560
- (_d = brand.website) != null ? _d : "",
18561
- (_e = brand.description) != null ? _e : "",
18562
- String((_f = brand.isActive) != null ? _f : "")
18563
- ];
18564
- return values.map((v) => this.formatCsvValue(v)).join(",");
18565
- }
18566
- formatDateIso(value) {
18567
- if (value instanceof Date) return value.toISOString();
18568
- if (value && typeof value.toDate === "function") {
18569
- const d = value.toDate();
18570
- return d instanceof Date ? d.toISOString() : String(value);
18571
- }
18572
- return String(value != null ? value : "");
18573
- }
18574
- formatCsvValue(value) {
18575
- const str = value === null || value === void 0 ? "" : String(value);
18576
- const escaped = str.replace(/"/g, '""');
18577
- return `"${escaped}"`;
18578
- }
18579
18523
  };
18580
18524
 
18581
18525
  // src/backoffice/services/category.service.ts
@@ -18767,71 +18711,6 @@ var CategoryService = class extends BaseService {
18767
18711
  ...docSnap.data()
18768
18712
  };
18769
18713
  }
18770
- /**
18771
- * Exports categories to CSV string, suitable for Excel/Sheets.
18772
- * Includes headers and optional UTF-8 BOM.
18773
- * By default exports only active categories (set includeInactive to true to export all).
18774
- */
18775
- async exportToCsv(options) {
18776
- var _a, _b;
18777
- const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
18778
- const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
18779
- const headers = [
18780
- "id",
18781
- "name",
18782
- "description",
18783
- "family",
18784
- "isActive"
18785
- ];
18786
- const rows = [];
18787
- rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
18788
- const PAGE_SIZE = 1e3;
18789
- let cursor;
18790
- const constraints = [];
18791
- if (!includeInactive) {
18792
- constraints.push(where34("isActive", "==", true));
18793
- }
18794
- constraints.push(orderBy20("name"));
18795
- while (true) {
18796
- const queryConstraints = [...constraints, limit18(PAGE_SIZE)];
18797
- if (cursor) queryConstraints.push(startAfter16(cursor));
18798
- const q = query34(this.categoriesRef, ...queryConstraints);
18799
- const snapshot = await getDocs34(q);
18800
- if (snapshot.empty) break;
18801
- for (const d of snapshot.docs) {
18802
- const category = { id: d.id, ...d.data() };
18803
- rows.push(this.categoryToCsvRow(category));
18804
- }
18805
- cursor = snapshot.docs[snapshot.docs.length - 1];
18806
- if (snapshot.size < PAGE_SIZE) break;
18807
- }
18808
- const csvBody = rows.join("\r\n");
18809
- return includeBom ? "\uFEFF" + csvBody : csvBody;
18810
- }
18811
- categoryToCsvRow(category) {
18812
- var _a, _b, _c, _d, _e;
18813
- const values = [
18814
- (_a = category.id) != null ? _a : "",
18815
- (_b = category.name) != null ? _b : "",
18816
- (_c = category.description) != null ? _c : "",
18817
- (_d = category.family) != null ? _d : "",
18818
- String((_e = category.isActive) != null ? _e : "")
18819
- ];
18820
- return values.map((v) => this.formatCsvValue(v)).join(",");
18821
- }
18822
- formatDateIso(value) {
18823
- if (value instanceof Date) return value.toISOString();
18824
- if (value && typeof value.toDate === "function") {
18825
- const d = value.toDate();
18826
- return d instanceof Date ? d.toISOString() : String(value);
18827
- }
18828
- return String(value != null ? value : "");
18829
- }
18830
- formatCsvValue(value) {
18831
- const str = value === null || value === void 0 ? "" : String(value);
18832
- const escaped = str.replace(/"/g, '""');
18833
- return `"${escaped}"`;
18834
- }
18835
18714
  };
18836
18715
 
18837
18716
  // src/backoffice/services/subcategory.service.ts
@@ -19075,74 +18954,6 @@ var SubcategoryService = class extends BaseService {
19075
18954
  ...docSnap.data()
19076
18955
  };
19077
18956
  }
19078
- /**
19079
- * Exports subcategories to CSV string, suitable for Excel/Sheets.
19080
- * Includes headers and optional UTF-8 BOM.
19081
- * By default exports only active subcategories (set includeInactive to true to export all).
19082
- */
19083
- async exportToCsv(options) {
19084
- var _a, _b;
19085
- const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
19086
- const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
19087
- const headers = [
19088
- "id",
19089
- "name",
19090
- "categoryId",
19091
- "description",
19092
- "isActive"
19093
- ];
19094
- const rows = [];
19095
- rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
19096
- const PAGE_SIZE = 1e3;
19097
- let cursor;
19098
- const constraints = [];
19099
- if (!includeInactive) {
19100
- constraints.push(where35("isActive", "==", true));
19101
- }
19102
- constraints.push(orderBy21("name"));
19103
- while (true) {
19104
- const queryConstraints = [...constraints, limit19(PAGE_SIZE)];
19105
- if (cursor) queryConstraints.push(startAfter17(cursor));
19106
- const q = query35(
19107
- collectionGroup2(this.db, SUBCATEGORIES_COLLECTION),
19108
- ...queryConstraints
19109
- );
19110
- const snapshot = await getDocs35(q);
19111
- if (snapshot.empty) break;
19112
- for (const d of snapshot.docs) {
19113
- const subcategory = { id: d.id, ...d.data() };
19114
- rows.push(this.subcategoryToCsvRow(subcategory));
19115
- }
19116
- cursor = snapshot.docs[snapshot.docs.length - 1];
19117
- if (snapshot.size < PAGE_SIZE) break;
19118
- }
19119
- const csvBody = rows.join("\r\n");
19120
- return includeBom ? "\uFEFF" + csvBody : csvBody;
19121
- }
19122
- subcategoryToCsvRow(subcategory) {
19123
- var _a, _b, _c, _d, _e;
19124
- const values = [
19125
- (_a = subcategory.id) != null ? _a : "",
19126
- (_b = subcategory.name) != null ? _b : "",
19127
- (_c = subcategory.categoryId) != null ? _c : "",
19128
- (_d = subcategory.description) != null ? _d : "",
19129
- String((_e = subcategory.isActive) != null ? _e : "")
19130
- ];
19131
- return values.map((v) => this.formatCsvValue(v)).join(",");
19132
- }
19133
- formatDateIso(value) {
19134
- if (value instanceof Date) return value.toISOString();
19135
- if (value && typeof value.toDate === "function") {
19136
- const d = value.toDate();
19137
- return d instanceof Date ? d.toISOString() : String(value);
19138
- }
19139
- return String(value != null ? value : "");
19140
- }
19141
- formatCsvValue(value) {
19142
- const str = value === null || value === void 0 ? "" : String(value);
19143
- const escaped = str.replace(/"/g, '""');
19144
- return `"${escaped}"`;
19145
- }
19146
18957
  };
19147
18958
 
19148
18959
  // src/backoffice/services/technology.service.ts
@@ -19159,14 +18970,8 @@ import {
19159
18970
  updateDoc as updateDoc38,
19160
18971
  where as where36,
19161
18972
  arrayUnion as arrayUnion9,
19162
- arrayRemove as arrayRemove8,
19163
- writeBatch as writeBatch7
18973
+ arrayRemove as arrayRemove8
19164
18974
  } from "firebase/firestore";
19165
-
19166
- // src/backoffice/types/product.types.ts
19167
- var PRODUCTS_COLLECTION = "products";
19168
-
19169
- // src/backoffice/services/technology.service.ts
19170
18975
  var DEFAULT_CERTIFICATION_REQUIREMENT = {
19171
18976
  minimumLevel: "aesthetician" /* AESTHETICIAN */,
19172
18977
  requiredSpecialties: []
@@ -19328,18 +19133,7 @@ var TechnologyService = class extends BaseService {
19328
19133
  });
19329
19134
  updateData.updatedAt = /* @__PURE__ */ new Date();
19330
19135
  const docRef = doc41(this.technologiesRef, id);
19331
- const beforeTech = await this.getById(id);
19332
19136
  await updateDoc38(docRef, updateData);
19333
- const categoryChanged = beforeTech && updateData.categoryId && beforeTech.categoryId !== updateData.categoryId;
19334
- const subcategoryChanged = beforeTech && updateData.subcategoryId && beforeTech.subcategoryId !== updateData.subcategoryId;
19335
- const nameChanged = beforeTech && updateData.name && beforeTech.name !== updateData.name;
19336
- if (categoryChanged || subcategoryChanged || nameChanged) {
19337
- await this.updateProductsInSubcollection(id, {
19338
- categoryId: updateData.categoryId,
19339
- subcategoryId: updateData.subcategoryId,
19340
- technologyName: updateData.name
19341
- });
19342
- }
19343
19137
  return this.getById(id);
19344
19138
  }
19345
19139
  /**
@@ -19775,225 +19569,6 @@ var TechnologyService = class extends BaseService {
19775
19569
  })
19776
19570
  );
19777
19571
  }
19778
- // ==========================================
19779
- // NEW METHODS: Product assignment management
19780
- // ==========================================
19781
- /**
19782
- * Assigns multiple products to a technology
19783
- * Updates each product's assignedTechnologyIds array
19784
- */
19785
- async assignProducts(technologyId, productIds) {
19786
- const batch = writeBatch7(this.db);
19787
- for (const productId of productIds) {
19788
- const productRef = doc41(this.db, PRODUCTS_COLLECTION, productId);
19789
- batch.update(productRef, {
19790
- assignedTechnologyIds: arrayUnion9(technologyId),
19791
- updatedAt: /* @__PURE__ */ new Date()
19792
- });
19793
- }
19794
- await batch.commit();
19795
- }
19796
- /**
19797
- * Unassigns multiple products from a technology
19798
- * Updates each product's assignedTechnologyIds array
19799
- */
19800
- async unassignProducts(technologyId, productIds) {
19801
- const batch = writeBatch7(this.db);
19802
- for (const productId of productIds) {
19803
- const productRef = doc41(this.db, PRODUCTS_COLLECTION, productId);
19804
- batch.update(productRef, {
19805
- assignedTechnologyIds: arrayRemove8(technologyId),
19806
- updatedAt: /* @__PURE__ */ new Date()
19807
- });
19808
- }
19809
- await batch.commit();
19810
- }
19811
- /**
19812
- * Gets products assigned to a specific technology
19813
- * Reads from top-level collection for immediate consistency (Cloud Functions may lag)
19814
- */
19815
- async getAssignedProducts(technologyId) {
19816
- const q = query36(
19817
- collection36(this.db, PRODUCTS_COLLECTION),
19818
- where36("assignedTechnologyIds", "array-contains", technologyId),
19819
- where36("isActive", "==", true),
19820
- orderBy22("name")
19821
- );
19822
- const snapshot = await getDocs36(q);
19823
- return snapshot.docs.map(
19824
- (doc44) => ({
19825
- id: doc44.id,
19826
- ...doc44.data()
19827
- })
19828
- );
19829
- }
19830
- /**
19831
- * Gets products NOT assigned to a specific technology
19832
- */
19833
- async getUnassignedProducts(technologyId) {
19834
- const q = query36(
19835
- collection36(this.db, PRODUCTS_COLLECTION),
19836
- where36("isActive", "==", true),
19837
- orderBy22("name")
19838
- );
19839
- const snapshot = await getDocs36(q);
19840
- const allProducts = snapshot.docs.map(
19841
- (doc44) => ({
19842
- id: doc44.id,
19843
- ...doc44.data()
19844
- })
19845
- );
19846
- return allProducts.filter(
19847
- (product) => {
19848
- var _a;
19849
- return !((_a = product.assignedTechnologyIds) == null ? void 0 : _a.includes(technologyId));
19850
- }
19851
- );
19852
- }
19853
- /**
19854
- * Gets product assignment statistics for a technology
19855
- */
19856
- async getProductStats(technologyId) {
19857
- const products = await this.getAssignedProducts(technologyId);
19858
- const byBrand = {};
19859
- products.forEach((product) => {
19860
- byBrand[product.brandName] = (byBrand[product.brandName] || 0) + 1;
19861
- });
19862
- return {
19863
- totalAssigned: products.length,
19864
- byBrand
19865
- };
19866
- }
19867
- /**
19868
- * Updates products in technology subcollection when technology metadata changes
19869
- * @param technologyId - ID of the technology
19870
- * @param updates - Fields to update (categoryId, subcategoryId, technologyName)
19871
- */
19872
- async updateProductsInSubcollection(technologyId, updates) {
19873
- const productsRef = collection36(this.db, TECHNOLOGIES_COLLECTION, technologyId, PRODUCTS_COLLECTION);
19874
- const productsSnapshot = await getDocs36(productsRef);
19875
- if (productsSnapshot.empty) {
19876
- return;
19877
- }
19878
- const batch = writeBatch7(this.db);
19879
- for (const productDoc of productsSnapshot.docs) {
19880
- const productRef = productDoc.ref;
19881
- const updateFields = {};
19882
- if (updates.categoryId !== void 0) {
19883
- updateFields.categoryId = updates.categoryId;
19884
- }
19885
- if (updates.subcategoryId !== void 0) {
19886
- updateFields.subcategoryId = updates.subcategoryId;
19887
- }
19888
- if (updates.technologyName !== void 0) {
19889
- updateFields.technologyName = updates.technologyName;
19890
- }
19891
- if (Object.keys(updateFields).length > 0) {
19892
- batch.update(productRef, updateFields);
19893
- }
19894
- }
19895
- await batch.commit();
19896
- }
19897
- /**
19898
- * Exports technologies to CSV string, suitable for Excel/Sheets.
19899
- * Includes headers and optional UTF-8 BOM.
19900
- * By default exports only active technologies (set includeInactive to true to export all).
19901
- * Includes product names from subcollections.
19902
- */
19903
- async exportToCsv(options) {
19904
- var _a, _b;
19905
- const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
19906
- const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
19907
- const headers = [
19908
- "id",
19909
- "name",
19910
- "description",
19911
- "family",
19912
- "categoryId",
19913
- "subcategoryId",
19914
- "technicalDetails",
19915
- "requirements_pre",
19916
- "requirements_post",
19917
- "blockingConditions",
19918
- "contraindications",
19919
- "benefits",
19920
- "certificationMinimumLevel",
19921
- "certificationRequiredSpecialties",
19922
- "documentationTemplateIds",
19923
- "productNames",
19924
- "isActive"
19925
- ];
19926
- const rows = [];
19927
- rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
19928
- const PAGE_SIZE = 1e3;
19929
- let cursor;
19930
- const constraints = [];
19931
- if (!includeInactive) {
19932
- constraints.push(where36("isActive", "==", true));
19933
- }
19934
- constraints.push(orderBy22("name"));
19935
- while (true) {
19936
- const queryConstraints = [...constraints, limit20(PAGE_SIZE)];
19937
- if (cursor) queryConstraints.push(startAfter18(cursor));
19938
- const q = query36(this.technologiesRef, ...queryConstraints);
19939
- const snapshot = await getDocs36(q);
19940
- if (snapshot.empty) break;
19941
- for (const d of snapshot.docs) {
19942
- const technology = { id: d.id, ...d.data() };
19943
- const productNames = await this.getProductNamesForTechnology(technology.id);
19944
- rows.push(this.technologyToCsvRow(technology, productNames));
19945
- }
19946
- cursor = snapshot.docs[snapshot.docs.length - 1];
19947
- if (snapshot.size < PAGE_SIZE) break;
19948
- }
19949
- const csvBody = rows.join("\r\n");
19950
- return includeBom ? "\uFEFF" + csvBody : csvBody;
19951
- }
19952
- /**
19953
- * Gets product names from the technology's product subcollection
19954
- */
19955
- async getProductNamesForTechnology(technologyId) {
19956
- try {
19957
- const productsRef = collection36(this.db, TECHNOLOGIES_COLLECTION, technologyId, PRODUCTS_COLLECTION);
19958
- const q = query36(productsRef, where36("isActive", "==", true));
19959
- const snapshot = await getDocs36(q);
19960
- return snapshot.docs.map((doc44) => {
19961
- const product = doc44.data();
19962
- return product.name || "";
19963
- }).filter((name) => name);
19964
- } catch (error) {
19965
- console.error(`Error fetching products for technology ${technologyId}:`, error);
19966
- return [];
19967
- }
19968
- }
19969
- technologyToCsvRow(technology, productNames = []) {
19970
- 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;
19971
- const values = [
19972
- (_a = technology.id) != null ? _a : "",
19973
- (_b = technology.name) != null ? _b : "",
19974
- (_c = technology.description) != null ? _c : "",
19975
- (_d = technology.family) != null ? _d : "",
19976
- (_e = technology.categoryId) != null ? _e : "",
19977
- (_f = technology.subcategoryId) != null ? _f : "",
19978
- (_g = technology.technicalDetails) != null ? _g : "",
19979
- (_j = (_i = (_h = technology.requirements) == null ? void 0 : _h.pre) == null ? void 0 : _i.map((r) => r.name).join(";")) != null ? _j : "",
19980
- (_m = (_l = (_k = technology.requirements) == null ? void 0 : _k.post) == null ? void 0 : _l.map((r) => r.name).join(";")) != null ? _m : "",
19981
- (_o = (_n = technology.blockingConditions) == null ? void 0 : _n.join(";")) != null ? _o : "",
19982
- (_q = (_p = technology.contraindications) == null ? void 0 : _p.map((c) => c.name).join(";")) != null ? _q : "",
19983
- (_s = (_r = technology.benefits) == null ? void 0 : _r.map((b) => b.name).join(";")) != null ? _s : "",
19984
- (_u = (_t = technology.certificationRequirement) == null ? void 0 : _t.minimumLevel) != null ? _u : "",
19985
- (_x = (_w = (_v = technology.certificationRequirement) == null ? void 0 : _v.requiredSpecialties) == null ? void 0 : _w.join(";")) != null ? _x : "",
19986
- (_z = (_y = technology.documentationTemplates) == null ? void 0 : _y.map((t) => t.templateId).join(";")) != null ? _z : "",
19987
- productNames.join(";"),
19988
- String((_A = technology.isActive) != null ? _A : "")
19989
- ];
19990
- return values.map((v) => this.formatCsvValue(v)).join(",");
19991
- }
19992
- formatCsvValue(value) {
19993
- const str = value === null || value === void 0 ? "" : String(value);
19994
- const escaped = str.replace(/"/g, '""');
19995
- return `"${escaped}"`;
19996
- }
19997
19572
  };
19998
19573
 
19999
19574
  // src/backoffice/services/product.service.ts
@@ -20010,20 +19585,16 @@ import {
20010
19585
  limit as limit21,
20011
19586
  orderBy as orderBy23,
20012
19587
  startAfter as startAfter19,
20013
- getCountFromServer as getCountFromServer7,
20014
- arrayUnion as arrayUnion10,
20015
- arrayRemove as arrayRemove9
19588
+ getCountFromServer as getCountFromServer7
20016
19589
  } from "firebase/firestore";
19590
+
19591
+ // src/backoffice/types/product.types.ts
19592
+ var PRODUCTS_COLLECTION = "products";
19593
+
19594
+ // src/backoffice/services/product.service.ts
20017
19595
  var ProductService = class extends BaseService {
20018
19596
  /**
20019
- * Gets reference to top-level products collection (source of truth)
20020
- * @returns Firestore collection reference
20021
- */
20022
- getTopLevelProductsRef() {
20023
- return collection37(this.db, PRODUCTS_COLLECTION);
20024
- }
20025
- /**
20026
- * Gets reference to products collection under a technology (backward compatibility)
19597
+ * Gets reference to products collection under a technology
20027
19598
  * @param technologyId - ID of the technology
20028
19599
  * @returns Firestore collection reference
20029
19600
  */
@@ -20039,7 +19610,6 @@ var ProductService = class extends BaseService {
20039
19610
  ...product,
20040
19611
  brandId,
20041
19612
  technologyId,
20042
- // Required for old subcollection structure
20043
19613
  createdAt: now,
20044
19614
  updatedAt: now,
20045
19615
  isActive: true
@@ -20099,26 +19669,30 @@ var ProductService = class extends BaseService {
20099
19669
  }
20100
19670
  /**
20101
19671
  * Gets counts of active products grouped by category, subcategory, and technology.
20102
- * Queries technology subcollections which have the legacy fields synced by Cloud Functions.
19672
+ * This uses a single collectionGroup query for efficiency.
20103
19673
  */
20104
19674
  async getProductCounts() {
19675
+ const q = query37(collectionGroup3(this.db, PRODUCTS_COLLECTION), where37("isActive", "==", true));
19676
+ const snapshot = await getDocs37(q);
20105
19677
  const counts = {
20106
19678
  byCategory: {},
20107
19679
  bySubcategory: {},
20108
19680
  byTechnology: {}
20109
19681
  };
20110
- const q = query37(collectionGroup3(this.db, PRODUCTS_COLLECTION), where37("isActive", "==", true));
20111
- const snapshot = await getDocs37(q);
19682
+ if (snapshot.empty) {
19683
+ return counts;
19684
+ }
20112
19685
  snapshot.docs.forEach((doc44) => {
20113
19686
  const product = doc44.data();
20114
- if (product.categoryId) {
20115
- counts.byCategory[product.categoryId] = (counts.byCategory[product.categoryId] || 0) + 1;
19687
+ const { categoryId, subcategoryId, technologyId } = product;
19688
+ if (categoryId) {
19689
+ counts.byCategory[categoryId] = (counts.byCategory[categoryId] || 0) + 1;
20116
19690
  }
20117
- if (product.subcategoryId) {
20118
- counts.bySubcategory[product.subcategoryId] = (counts.bySubcategory[product.subcategoryId] || 0) + 1;
19691
+ if (subcategoryId) {
19692
+ counts.bySubcategory[subcategoryId] = (counts.bySubcategory[subcategoryId] || 0) + 1;
20119
19693
  }
20120
- if (product.technologyId) {
20121
- counts.byTechnology[product.technologyId] = (counts.byTechnology[product.technologyId] || 0) + 1;
19694
+ if (technologyId) {
19695
+ counts.byTechnology[technologyId] = (counts.byTechnology[technologyId] || 0) + 1;
20122
19696
  }
20123
19697
  });
20124
19698
  return counts;
@@ -20197,247 +19771,12 @@ var ProductService = class extends BaseService {
20197
19771
  ...docSnap.data()
20198
19772
  };
20199
19773
  }
20200
- // ==========================================
20201
- // NEW METHODS: Top-level collection (preferred)
20202
- // ==========================================
20203
- /**
20204
- * Creates a new product in the top-level collection
20205
- */
20206
- async createTopLevel(brandId, product, technologyIds = []) {
20207
- const now = /* @__PURE__ */ new Date();
20208
- const newProduct = {
20209
- ...product,
20210
- brandId,
20211
- assignedTechnologyIds: technologyIds,
20212
- createdAt: now,
20213
- updatedAt: now,
20214
- isActive: true
20215
- };
20216
- const productRef = await addDoc8(this.getTopLevelProductsRef(), newProduct);
20217
- return { id: productRef.id, ...newProduct };
20218
- }
20219
- /**
20220
- * Gets all products from the top-level collection
20221
- */
20222
- async getAllTopLevel(options) {
20223
- const { rowsPerPage, lastVisible, brandId } = options;
20224
- const constraints = [where37("isActive", "==", true), orderBy23("name")];
20225
- if (brandId) {
20226
- constraints.push(where37("brandId", "==", brandId));
20227
- }
20228
- if (lastVisible) {
20229
- constraints.push(startAfter19(lastVisible));
20230
- }
20231
- constraints.push(limit21(rowsPerPage));
20232
- const q = query37(this.getTopLevelProductsRef(), ...constraints);
20233
- const snapshot = await getDocs37(q);
20234
- const products = snapshot.docs.map(
20235
- (doc44) => ({
20236
- id: doc44.id,
20237
- ...doc44.data()
20238
- })
20239
- );
20240
- const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
20241
- return { products, lastVisible: newLastVisible };
20242
- }
20243
- /**
20244
- * Gets a product by ID from the top-level collection
20245
- */
20246
- async getByIdTopLevel(productId) {
20247
- const docRef = doc42(this.getTopLevelProductsRef(), productId);
20248
- const docSnap = await getDoc43(docRef);
20249
- if (!docSnap.exists()) return null;
20250
- return {
20251
- id: docSnap.id,
20252
- ...docSnap.data()
20253
- };
20254
- }
20255
- /**
20256
- * Updates a product in the top-level collection
20257
- */
20258
- async updateTopLevel(productId, product) {
20259
- const updateData = {
20260
- ...product,
20261
- updatedAt: /* @__PURE__ */ new Date()
20262
- };
20263
- const docRef = doc42(this.getTopLevelProductsRef(), productId);
20264
- await updateDoc39(docRef, updateData);
20265
- return this.getByIdTopLevel(productId);
20266
- }
20267
- /**
20268
- * Deletes a product from the top-level collection (soft delete)
20269
- */
20270
- async deleteTopLevel(productId) {
20271
- await this.updateTopLevel(productId, {
20272
- isActive: false
20273
- });
20274
- }
20275
- /**
20276
- * Assigns a product to a technology
20277
- */
20278
- async assignToTechnology(productId, technologyId) {
20279
- const docRef = doc42(this.getTopLevelProductsRef(), productId);
20280
- await updateDoc39(docRef, {
20281
- assignedTechnologyIds: arrayUnion10(technologyId),
20282
- updatedAt: /* @__PURE__ */ new Date()
20283
- });
20284
- }
20285
- /**
20286
- * Unassigns a product from a technology
20287
- */
20288
- async unassignFromTechnology(productId, technologyId) {
20289
- const docRef = doc42(this.getTopLevelProductsRef(), productId);
20290
- await updateDoc39(docRef, {
20291
- assignedTechnologyIds: arrayRemove9(technologyId),
20292
- updatedAt: /* @__PURE__ */ new Date()
20293
- });
20294
- }
20295
- /**
20296
- * Gets products assigned to a specific technology
20297
- */
20298
- async getAssignedProducts(technologyId) {
20299
- const q = query37(
20300
- this.getTopLevelProductsRef(),
20301
- where37("assignedTechnologyIds", "array-contains", technologyId),
20302
- where37("isActive", "==", true),
20303
- orderBy23("name")
20304
- );
20305
- const snapshot = await getDocs37(q);
20306
- return snapshot.docs.map(
20307
- (doc44) => ({
20308
- id: doc44.id,
20309
- ...doc44.data()
20310
- })
20311
- );
20312
- }
20313
- /**
20314
- * Gets products NOT assigned to a specific technology
20315
- */
20316
- async getUnassignedProducts(technologyId) {
20317
- const q = query37(
20318
- this.getTopLevelProductsRef(),
20319
- where37("isActive", "==", true),
20320
- orderBy23("name")
20321
- );
20322
- const snapshot = await getDocs37(q);
20323
- const allProducts = snapshot.docs.map(
20324
- (doc44) => ({
20325
- id: doc44.id,
20326
- ...doc44.data()
20327
- })
20328
- );
20329
- return allProducts.filter(
20330
- (product) => {
20331
- var _a;
20332
- return !((_a = product.assignedTechnologyIds) == null ? void 0 : _a.includes(technologyId));
20333
- }
20334
- );
20335
- }
20336
- /**
20337
- * Gets all products for a brand (from top-level collection)
20338
- */
20339
- async getByBrand(brandId) {
20340
- const q = query37(
20341
- this.getTopLevelProductsRef(),
20342
- where37("brandId", "==", brandId),
20343
- where37("isActive", "==", true),
20344
- orderBy23("name")
20345
- );
20346
- const snapshot = await getDocs37(q);
20347
- return snapshot.docs.map(
20348
- (doc44) => ({
20349
- id: doc44.id,
20350
- ...doc44.data()
20351
- })
20352
- );
20353
- }
20354
- /**
20355
- * Exports products to CSV string, suitable for Excel/Sheets.
20356
- * Includes headers and optional UTF-8 BOM.
20357
- * By default exports only active products (set includeInactive to true to export all).
20358
- */
20359
- async exportToCsv(options) {
20360
- var _a, _b;
20361
- const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
20362
- const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
20363
- const headers = [
20364
- "id",
20365
- "name",
20366
- "brandId",
20367
- "brandName",
20368
- "assignedTechnologyIds",
20369
- "description",
20370
- "technicalDetails",
20371
- "dosage",
20372
- "composition",
20373
- "indications",
20374
- "contraindications",
20375
- "warnings",
20376
- "isActive"
20377
- ];
20378
- const rows = [];
20379
- rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
20380
- const PAGE_SIZE = 1e3;
20381
- let cursor;
20382
- const constraints = [];
20383
- if (!includeInactive) {
20384
- constraints.push(where37("isActive", "==", true));
20385
- }
20386
- constraints.push(orderBy23("name"));
20387
- while (true) {
20388
- const queryConstraints = [...constraints, limit21(PAGE_SIZE)];
20389
- if (cursor) queryConstraints.push(startAfter19(cursor));
20390
- const q = query37(this.getTopLevelProductsRef(), ...queryConstraints);
20391
- const snapshot = await getDocs37(q);
20392
- if (snapshot.empty) break;
20393
- for (const d of snapshot.docs) {
20394
- const product = { id: d.id, ...d.data() };
20395
- rows.push(this.productToCsvRow(product));
20396
- }
20397
- cursor = snapshot.docs[snapshot.docs.length - 1];
20398
- if (snapshot.size < PAGE_SIZE) break;
20399
- }
20400
- const csvBody = rows.join("\r\n");
20401
- return includeBom ? "\uFEFF" + csvBody : csvBody;
20402
- }
20403
- productToCsvRow(product) {
20404
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q;
20405
- const values = [
20406
- (_a = product.id) != null ? _a : "",
20407
- (_b = product.name) != null ? _b : "",
20408
- (_c = product.brandId) != null ? _c : "",
20409
- (_d = product.brandName) != null ? _d : "",
20410
- (_f = (_e = product.assignedTechnologyIds) == null ? void 0 : _e.join(";")) != null ? _f : "",
20411
- (_g = product.description) != null ? _g : "",
20412
- (_h = product.technicalDetails) != null ? _h : "",
20413
- (_i = product.dosage) != null ? _i : "",
20414
- (_j = product.composition) != null ? _j : "",
20415
- (_l = (_k = product.indications) == null ? void 0 : _k.join(";")) != null ? _l : "",
20416
- (_n = (_m = product.contraindications) == null ? void 0 : _m.map((c) => c.name).join(";")) != null ? _n : "",
20417
- (_p = (_o = product.warnings) == null ? void 0 : _o.join(";")) != null ? _p : "",
20418
- String((_q = product.isActive) != null ? _q : "")
20419
- ];
20420
- return values.map((v) => this.formatCsvValue(v)).join(",");
20421
- }
20422
- formatDateIso(value) {
20423
- if (value instanceof Date) return value.toISOString();
20424
- if (value && typeof value.toDate === "function") {
20425
- const d = value.toDate();
20426
- return d instanceof Date ? d.toISOString() : String(value);
20427
- }
20428
- return String(value != null ? value : "");
20429
- }
20430
- formatCsvValue(value) {
20431
- const str = value === null || value === void 0 ? "" : String(value);
20432
- const escaped = str.replace(/"/g, '""');
20433
- return `"${escaped}"`;
20434
- }
20435
19774
  };
20436
19775
 
20437
19776
  // src/backoffice/services/constants.service.ts
20438
19777
  import {
20439
- arrayRemove as arrayRemove10,
20440
- arrayUnion as arrayUnion11,
19778
+ arrayRemove as arrayRemove9,
19779
+ arrayUnion as arrayUnion10,
20441
19780
  doc as doc43,
20442
19781
  getDoc as getDoc44,
20443
19782
  setDoc as setDoc30,
@@ -20505,7 +19844,7 @@ var ConstantsService = class extends BaseService {
20505
19844
  await setDoc30(this.treatmentBenefitsDocRef, { benefits: [newBenefit] });
20506
19845
  } else {
20507
19846
  await updateDoc40(this.treatmentBenefitsDocRef, {
20508
- benefits: arrayUnion11(newBenefit)
19847
+ benefits: arrayUnion10(newBenefit)
20509
19848
  });
20510
19849
  }
20511
19850
  return newBenefit;
@@ -20559,7 +19898,7 @@ var ConstantsService = class extends BaseService {
20559
19898
  return;
20560
19899
  }
20561
19900
  await updateDoc40(this.treatmentBenefitsDocRef, {
20562
- benefits: arrayRemove10(benefitToRemove)
19901
+ benefits: arrayRemove9(benefitToRemove)
20563
19902
  });
20564
19903
  }
20565
19904
  // =================================================================
@@ -20612,7 +19951,7 @@ var ConstantsService = class extends BaseService {
20612
19951
  });
20613
19952
  } else {
20614
19953
  await updateDoc40(this.contraindicationsDocRef, {
20615
- contraindications: arrayUnion11(newContraindication)
19954
+ contraindications: arrayUnion10(newContraindication)
20616
19955
  });
20617
19956
  }
20618
19957
  return newContraindication;
@@ -20668,69 +20007,9 @@ var ConstantsService = class extends BaseService {
20668
20007
  return;
20669
20008
  }
20670
20009
  await updateDoc40(this.contraindicationsDocRef, {
20671
- contraindications: arrayRemove10(toRemove)
20010
+ contraindications: arrayRemove9(toRemove)
20672
20011
  });
20673
20012
  }
20674
- // =================================================================
20675
- // CSV Export Methods
20676
- // =================================================================
20677
- /**
20678
- * Exports treatment benefits to CSV string, suitable for Excel/Sheets.
20679
- * Includes headers and optional UTF-8 BOM.
20680
- */
20681
- async exportBenefitsToCsv(options) {
20682
- var _a;
20683
- const includeBom = (_a = options == null ? void 0 : options.includeBom) != null ? _a : true;
20684
- const headers = ["id", "name", "description"];
20685
- const rows = [];
20686
- rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
20687
- const benefits = await this.getAllBenefitsForFilter();
20688
- for (const benefit of benefits) {
20689
- rows.push(this.benefitToCsvRow(benefit));
20690
- }
20691
- const csvBody = rows.join("\r\n");
20692
- return includeBom ? "\uFEFF" + csvBody : csvBody;
20693
- }
20694
- /**
20695
- * Exports contraindications to CSV string, suitable for Excel/Sheets.
20696
- * Includes headers and optional UTF-8 BOM.
20697
- */
20698
- async exportContraindicationsToCsv(options) {
20699
- var _a;
20700
- const includeBom = (_a = options == null ? void 0 : options.includeBom) != null ? _a : true;
20701
- const headers = ["id", "name", "description"];
20702
- const rows = [];
20703
- rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
20704
- const contraindications = await this.getAllContraindicationsForFilter();
20705
- for (const contraindication of contraindications) {
20706
- rows.push(this.contraindicationToCsvRow(contraindication));
20707
- }
20708
- const csvBody = rows.join("\r\n");
20709
- return includeBom ? "\uFEFF" + csvBody : csvBody;
20710
- }
20711
- benefitToCsvRow(benefit) {
20712
- var _a, _b, _c;
20713
- const values = [
20714
- (_a = benefit.id) != null ? _a : "",
20715
- (_b = benefit.name) != null ? _b : "",
20716
- (_c = benefit.description) != null ? _c : ""
20717
- ];
20718
- return values.map((v) => this.formatCsvValue(v)).join(",");
20719
- }
20720
- contraindicationToCsvRow(contraindication) {
20721
- var _a, _b, _c;
20722
- const values = [
20723
- (_a = contraindication.id) != null ? _a : "",
20724
- (_b = contraindication.name) != null ? _b : "",
20725
- (_c = contraindication.description) != null ? _c : ""
20726
- ];
20727
- return values.map((v) => this.formatCsvValue(v)).join(",");
20728
- }
20729
- formatCsvValue(value) {
20730
- const str = value === null || value === void 0 ? "" : String(value);
20731
- const escaped = str.replace(/"/g, '""');
20732
- return `"${escaped}"`;
20733
- }
20734
20013
  };
20735
20014
 
20736
20015
  // src/backoffice/types/static/contraindication.types.ts