@blackcode_sa/metaestetics-api 1.12.43 → 1.12.45

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.js CHANGED
@@ -1546,11 +1546,11 @@ function calculateItemSubtotal(item) {
1546
1546
  if (item.type === "note") {
1547
1547
  return 0;
1548
1548
  }
1549
+ const quantity = item.quantity || 0;
1549
1550
  if (item.priceOverrideAmount !== void 0 && item.priceOverrideAmount !== null) {
1550
- return item.priceOverrideAmount;
1551
+ return item.priceOverrideAmount * quantity;
1551
1552
  }
1552
1553
  const price = item.price || 0;
1553
- const quantity = item.quantity || 0;
1554
1554
  return price * quantity;
1555
1555
  }
1556
1556
  function calculateFinalBilling(zonesData, taxRate = 0.2) {
@@ -1666,7 +1666,18 @@ async function updateZoneItemUtil(db, appointmentId, zoneId, itemIndex, updates)
1666
1666
  ...updates,
1667
1667
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1668
1668
  };
1669
+ console.log(`[updateZoneItemUtil] BEFORE recalculation:`, {
1670
+ itemIndex,
1671
+ quantity: items[itemIndex].quantity,
1672
+ priceOverrideAmount: items[itemIndex].priceOverrideAmount,
1673
+ price: items[itemIndex].price,
1674
+ oldSubtotal: items[itemIndex].subtotal
1675
+ });
1669
1676
  items[itemIndex].subtotal = calculateItemSubtotal(items[itemIndex]);
1677
+ console.log(`[updateZoneItemUtil] AFTER recalculation:`, {
1678
+ itemIndex,
1679
+ newSubtotal: items[itemIndex].subtotal
1680
+ });
1670
1681
  const finalbilling = calculateFinalBilling(metadata.zonesData);
1671
1682
  const appointmentRef = (0, import_firestore5.doc)(db, APPOINTMENTS_COLLECTION, appointmentId);
1672
1683
  await (0, import_firestore4.updateDoc)(appointmentRef, {
@@ -18238,73 +18249,6 @@ var BrandService = class extends BaseService {
18238
18249
  ...docSnap.data()
18239
18250
  };
18240
18251
  }
18241
- /**
18242
- * Exports brands to CSV string, suitable for Excel/Sheets.
18243
- * Includes headers and optional UTF-8 BOM.
18244
- * By default exports only active brands (set includeInactive to true to export all).
18245
- */
18246
- async exportToCsv(options) {
18247
- var _a, _b;
18248
- const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
18249
- const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
18250
- const headers = [
18251
- "id",
18252
- "name",
18253
- "manufacturer",
18254
- "website",
18255
- "description",
18256
- "isActive"
18257
- ];
18258
- const rows = [];
18259
- rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
18260
- const PAGE_SIZE = 1e3;
18261
- let cursor;
18262
- const baseConstraints = [];
18263
- if (!includeInactive) {
18264
- baseConstraints.push((0, import_firestore58.where)("isActive", "==", true));
18265
- }
18266
- baseConstraints.push((0, import_firestore58.orderBy)("name_lowercase"));
18267
- while (true) {
18268
- const constraints = [...baseConstraints, (0, import_firestore58.limit)(PAGE_SIZE)];
18269
- if (cursor) constraints.push((0, import_firestore58.startAfter)(cursor));
18270
- const q = (0, import_firestore58.query)(this.getBrandsRef(), ...constraints);
18271
- const snapshot = await (0, import_firestore58.getDocs)(q);
18272
- if (snapshot.empty) break;
18273
- for (const d of snapshot.docs) {
18274
- const brand = { id: d.id, ...d.data() };
18275
- rows.push(this.brandToCsvRow(brand));
18276
- }
18277
- cursor = snapshot.docs[snapshot.docs.length - 1];
18278
- if (snapshot.size < PAGE_SIZE) break;
18279
- }
18280
- const csvBody = rows.join("\r\n");
18281
- return includeBom ? "\uFEFF" + csvBody : csvBody;
18282
- }
18283
- brandToCsvRow(brand) {
18284
- var _a, _b, _c, _d, _e, _f;
18285
- const values = [
18286
- (_a = brand.id) != null ? _a : "",
18287
- (_b = brand.name) != null ? _b : "",
18288
- (_c = brand.manufacturer) != null ? _c : "",
18289
- (_d = brand.website) != null ? _d : "",
18290
- (_e = brand.description) != null ? _e : "",
18291
- String((_f = brand.isActive) != null ? _f : "")
18292
- ];
18293
- return values.map((v) => this.formatCsvValue(v)).join(",");
18294
- }
18295
- formatDateIso(value) {
18296
- if (value instanceof Date) return value.toISOString();
18297
- if (value && typeof value.toDate === "function") {
18298
- const d = value.toDate();
18299
- return d instanceof Date ? d.toISOString() : String(value);
18300
- }
18301
- return String(value != null ? value : "");
18302
- }
18303
- formatCsvValue(value) {
18304
- const str = value === null || value === void 0 ? "" : String(value);
18305
- const escaped = str.replace(/"/g, '""');
18306
- return `"${escaped}"`;
18307
- }
18308
18252
  };
18309
18253
 
18310
18254
  // src/backoffice/services/category.service.ts
@@ -18483,71 +18427,6 @@ var CategoryService = class extends BaseService {
18483
18427
  ...docSnap.data()
18484
18428
  };
18485
18429
  }
18486
- /**
18487
- * Exports categories to CSV string, suitable for Excel/Sheets.
18488
- * Includes headers and optional UTF-8 BOM.
18489
- * By default exports only active categories (set includeInactive to true to export all).
18490
- */
18491
- async exportToCsv(options) {
18492
- var _a, _b;
18493
- const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
18494
- const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
18495
- const headers = [
18496
- "id",
18497
- "name",
18498
- "description",
18499
- "family",
18500
- "isActive"
18501
- ];
18502
- const rows = [];
18503
- rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
18504
- const PAGE_SIZE = 1e3;
18505
- let cursor;
18506
- const constraints = [];
18507
- if (!includeInactive) {
18508
- constraints.push((0, import_firestore59.where)("isActive", "==", true));
18509
- }
18510
- constraints.push((0, import_firestore59.orderBy)("name"));
18511
- while (true) {
18512
- const queryConstraints = [...constraints, (0, import_firestore59.limit)(PAGE_SIZE)];
18513
- if (cursor) queryConstraints.push((0, import_firestore59.startAfter)(cursor));
18514
- const q = (0, import_firestore59.query)(this.categoriesRef, ...queryConstraints);
18515
- const snapshot = await (0, import_firestore59.getDocs)(q);
18516
- if (snapshot.empty) break;
18517
- for (const d of snapshot.docs) {
18518
- const category = { id: d.id, ...d.data() };
18519
- rows.push(this.categoryToCsvRow(category));
18520
- }
18521
- cursor = snapshot.docs[snapshot.docs.length - 1];
18522
- if (snapshot.size < PAGE_SIZE) break;
18523
- }
18524
- const csvBody = rows.join("\r\n");
18525
- return includeBom ? "\uFEFF" + csvBody : csvBody;
18526
- }
18527
- categoryToCsvRow(category) {
18528
- var _a, _b, _c, _d, _e;
18529
- const values = [
18530
- (_a = category.id) != null ? _a : "",
18531
- (_b = category.name) != null ? _b : "",
18532
- (_c = category.description) != null ? _c : "",
18533
- (_d = category.family) != null ? _d : "",
18534
- String((_e = category.isActive) != null ? _e : "")
18535
- ];
18536
- return values.map((v) => this.formatCsvValue(v)).join(",");
18537
- }
18538
- formatDateIso(value) {
18539
- if (value instanceof Date) return value.toISOString();
18540
- if (value && typeof value.toDate === "function") {
18541
- const d = value.toDate();
18542
- return d instanceof Date ? d.toISOString() : String(value);
18543
- }
18544
- return String(value != null ? value : "");
18545
- }
18546
- formatCsvValue(value) {
18547
- const str = value === null || value === void 0 ? "" : String(value);
18548
- const escaped = str.replace(/"/g, '""');
18549
- return `"${escaped}"`;
18550
- }
18551
18430
  };
18552
18431
 
18553
18432
  // src/backoffice/services/subcategory.service.ts
@@ -18775,83 +18654,10 @@ var SubcategoryService = class extends BaseService {
18775
18654
  ...docSnap.data()
18776
18655
  };
18777
18656
  }
18778
- /**
18779
- * Exports subcategories to CSV string, suitable for Excel/Sheets.
18780
- * Includes headers and optional UTF-8 BOM.
18781
- * By default exports only active subcategories (set includeInactive to true to export all).
18782
- */
18783
- async exportToCsv(options) {
18784
- var _a, _b;
18785
- const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
18786
- const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
18787
- const headers = [
18788
- "id",
18789
- "name",
18790
- "categoryId",
18791
- "description",
18792
- "isActive"
18793
- ];
18794
- const rows = [];
18795
- rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
18796
- const PAGE_SIZE = 1e3;
18797
- let cursor;
18798
- const constraints = [];
18799
- if (!includeInactive) {
18800
- constraints.push((0, import_firestore60.where)("isActive", "==", true));
18801
- }
18802
- constraints.push((0, import_firestore60.orderBy)("name"));
18803
- while (true) {
18804
- const queryConstraints = [...constraints, (0, import_firestore60.limit)(PAGE_SIZE)];
18805
- if (cursor) queryConstraints.push((0, import_firestore60.startAfter)(cursor));
18806
- const q = (0, import_firestore60.query)(
18807
- (0, import_firestore60.collectionGroup)(this.db, SUBCATEGORIES_COLLECTION),
18808
- ...queryConstraints
18809
- );
18810
- const snapshot = await (0, import_firestore60.getDocs)(q);
18811
- if (snapshot.empty) break;
18812
- for (const d of snapshot.docs) {
18813
- const subcategory = { id: d.id, ...d.data() };
18814
- rows.push(this.subcategoryToCsvRow(subcategory));
18815
- }
18816
- cursor = snapshot.docs[snapshot.docs.length - 1];
18817
- if (snapshot.size < PAGE_SIZE) break;
18818
- }
18819
- const csvBody = rows.join("\r\n");
18820
- return includeBom ? "\uFEFF" + csvBody : csvBody;
18821
- }
18822
- subcategoryToCsvRow(subcategory) {
18823
- var _a, _b, _c, _d, _e;
18824
- const values = [
18825
- (_a = subcategory.id) != null ? _a : "",
18826
- (_b = subcategory.name) != null ? _b : "",
18827
- (_c = subcategory.categoryId) != null ? _c : "",
18828
- (_d = subcategory.description) != null ? _d : "",
18829
- String((_e = subcategory.isActive) != null ? _e : "")
18830
- ];
18831
- return values.map((v) => this.formatCsvValue(v)).join(",");
18832
- }
18833
- formatDateIso(value) {
18834
- if (value instanceof Date) return value.toISOString();
18835
- if (value && typeof value.toDate === "function") {
18836
- const d = value.toDate();
18837
- return d instanceof Date ? d.toISOString() : String(value);
18838
- }
18839
- return String(value != null ? value : "");
18840
- }
18841
- formatCsvValue(value) {
18842
- const str = value === null || value === void 0 ? "" : String(value);
18843
- const escaped = str.replace(/"/g, '""');
18844
- return `"${escaped}"`;
18845
- }
18846
18657
  };
18847
18658
 
18848
18659
  // src/backoffice/services/technology.service.ts
18849
18660
  var import_firestore61 = require("firebase/firestore");
18850
-
18851
- // src/backoffice/types/product.types.ts
18852
- var PRODUCTS_COLLECTION = "products";
18853
-
18854
- // src/backoffice/services/technology.service.ts
18855
18661
  var DEFAULT_CERTIFICATION_REQUIREMENT = {
18856
18662
  minimumLevel: "aesthetician" /* AESTHETICIAN */,
18857
18663
  requiredSpecialties: []
@@ -19013,18 +18819,7 @@ var TechnologyService = class extends BaseService {
19013
18819
  });
19014
18820
  updateData.updatedAt = /* @__PURE__ */ new Date();
19015
18821
  const docRef = (0, import_firestore61.doc)(this.technologiesRef, id);
19016
- const beforeTech = await this.getById(id);
19017
18822
  await (0, import_firestore61.updateDoc)(docRef, updateData);
19018
- const categoryChanged = beforeTech && updateData.categoryId && beforeTech.categoryId !== updateData.categoryId;
19019
- const subcategoryChanged = beforeTech && updateData.subcategoryId && beforeTech.subcategoryId !== updateData.subcategoryId;
19020
- const nameChanged = beforeTech && updateData.name && beforeTech.name !== updateData.name;
19021
- if (categoryChanged || subcategoryChanged || nameChanged) {
19022
- await this.updateProductsInSubcollection(id, {
19023
- categoryId: updateData.categoryId,
19024
- subcategoryId: updateData.subcategoryId,
19025
- technologyName: updateData.name
19026
- });
19027
- }
19028
18823
  return this.getById(id);
19029
18824
  }
19030
18825
  /**
@@ -19460,239 +19255,18 @@ var TechnologyService = class extends BaseService {
19460
19255
  })
19461
19256
  );
19462
19257
  }
19463
- // ==========================================
19464
- // NEW METHODS: Product assignment management
19465
- // ==========================================
19466
- /**
19467
- * Assigns multiple products to a technology
19468
- * Updates each product's assignedTechnologyIds array
19469
- */
19470
- async assignProducts(technologyId, productIds) {
19471
- const batch = (0, import_firestore61.writeBatch)(this.db);
19472
- for (const productId of productIds) {
19473
- const productRef = (0, import_firestore61.doc)(this.db, PRODUCTS_COLLECTION, productId);
19474
- batch.update(productRef, {
19475
- assignedTechnologyIds: (0, import_firestore61.arrayUnion)(technologyId),
19476
- updatedAt: /* @__PURE__ */ new Date()
19477
- });
19478
- }
19479
- await batch.commit();
19480
- }
19481
- /**
19482
- * Unassigns multiple products from a technology
19483
- * Updates each product's assignedTechnologyIds array
19484
- */
19485
- async unassignProducts(technologyId, productIds) {
19486
- const batch = (0, import_firestore61.writeBatch)(this.db);
19487
- for (const productId of productIds) {
19488
- const productRef = (0, import_firestore61.doc)(this.db, PRODUCTS_COLLECTION, productId);
19489
- batch.update(productRef, {
19490
- assignedTechnologyIds: (0, import_firestore61.arrayRemove)(technologyId),
19491
- updatedAt: /* @__PURE__ */ new Date()
19492
- });
19493
- }
19494
- await batch.commit();
19495
- }
19496
- /**
19497
- * Gets products assigned to a specific technology
19498
- * Reads from top-level collection for immediate consistency (Cloud Functions may lag)
19499
- */
19500
- async getAssignedProducts(technologyId) {
19501
- const q = (0, import_firestore61.query)(
19502
- (0, import_firestore61.collection)(this.db, PRODUCTS_COLLECTION),
19503
- (0, import_firestore61.where)("assignedTechnologyIds", "array-contains", technologyId),
19504
- (0, import_firestore61.where)("isActive", "==", true),
19505
- (0, import_firestore61.orderBy)("name")
19506
- );
19507
- const snapshot = await (0, import_firestore61.getDocs)(q);
19508
- return snapshot.docs.map(
19509
- (doc44) => ({
19510
- id: doc44.id,
19511
- ...doc44.data()
19512
- })
19513
- );
19514
- }
19515
- /**
19516
- * Gets products NOT assigned to a specific technology
19517
- */
19518
- async getUnassignedProducts(technologyId) {
19519
- const q = (0, import_firestore61.query)(
19520
- (0, import_firestore61.collection)(this.db, PRODUCTS_COLLECTION),
19521
- (0, import_firestore61.where)("isActive", "==", true),
19522
- (0, import_firestore61.orderBy)("name")
19523
- );
19524
- const snapshot = await (0, import_firestore61.getDocs)(q);
19525
- const allProducts = snapshot.docs.map(
19526
- (doc44) => ({
19527
- id: doc44.id,
19528
- ...doc44.data()
19529
- })
19530
- );
19531
- return allProducts.filter(
19532
- (product) => {
19533
- var _a;
19534
- return !((_a = product.assignedTechnologyIds) == null ? void 0 : _a.includes(technologyId));
19535
- }
19536
- );
19537
- }
19538
- /**
19539
- * Gets product assignment statistics for a technology
19540
- */
19541
- async getProductStats(technologyId) {
19542
- const products = await this.getAssignedProducts(technologyId);
19543
- const byBrand = {};
19544
- products.forEach((product) => {
19545
- byBrand[product.brandName] = (byBrand[product.brandName] || 0) + 1;
19546
- });
19547
- return {
19548
- totalAssigned: products.length,
19549
- byBrand
19550
- };
19551
- }
19552
- /**
19553
- * Updates products in technology subcollection when technology metadata changes
19554
- * @param technologyId - ID of the technology
19555
- * @param updates - Fields to update (categoryId, subcategoryId, technologyName)
19556
- */
19557
- async updateProductsInSubcollection(technologyId, updates) {
19558
- const productsRef = (0, import_firestore61.collection)(this.db, TECHNOLOGIES_COLLECTION, technologyId, PRODUCTS_COLLECTION);
19559
- const productsSnapshot = await (0, import_firestore61.getDocs)(productsRef);
19560
- if (productsSnapshot.empty) {
19561
- return;
19562
- }
19563
- const batch = (0, import_firestore61.writeBatch)(this.db);
19564
- for (const productDoc of productsSnapshot.docs) {
19565
- const productRef = productDoc.ref;
19566
- const updateFields = {};
19567
- if (updates.categoryId !== void 0) {
19568
- updateFields.categoryId = updates.categoryId;
19569
- }
19570
- if (updates.subcategoryId !== void 0) {
19571
- updateFields.subcategoryId = updates.subcategoryId;
19572
- }
19573
- if (updates.technologyName !== void 0) {
19574
- updateFields.technologyName = updates.technologyName;
19575
- }
19576
- if (Object.keys(updateFields).length > 0) {
19577
- batch.update(productRef, updateFields);
19578
- }
19579
- }
19580
- await batch.commit();
19581
- }
19582
- /**
19583
- * Exports technologies to CSV string, suitable for Excel/Sheets.
19584
- * Includes headers and optional UTF-8 BOM.
19585
- * By default exports only active technologies (set includeInactive to true to export all).
19586
- * Includes product names from subcollections.
19587
- */
19588
- async exportToCsv(options) {
19589
- var _a, _b;
19590
- const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
19591
- const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
19592
- const headers = [
19593
- "id",
19594
- "name",
19595
- "description",
19596
- "family",
19597
- "categoryId",
19598
- "subcategoryId",
19599
- "technicalDetails",
19600
- "requirements_pre",
19601
- "requirements_post",
19602
- "blockingConditions",
19603
- "contraindications",
19604
- "benefits",
19605
- "certificationMinimumLevel",
19606
- "certificationRequiredSpecialties",
19607
- "documentationTemplateIds",
19608
- "productNames",
19609
- "isActive"
19610
- ];
19611
- const rows = [];
19612
- rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
19613
- const PAGE_SIZE = 1e3;
19614
- let cursor;
19615
- const constraints = [];
19616
- if (!includeInactive) {
19617
- constraints.push((0, import_firestore61.where)("isActive", "==", true));
19618
- }
19619
- constraints.push((0, import_firestore61.orderBy)("name"));
19620
- while (true) {
19621
- const queryConstraints = [...constraints, (0, import_firestore61.limit)(PAGE_SIZE)];
19622
- if (cursor) queryConstraints.push((0, import_firestore61.startAfter)(cursor));
19623
- const q = (0, import_firestore61.query)(this.technologiesRef, ...queryConstraints);
19624
- const snapshot = await (0, import_firestore61.getDocs)(q);
19625
- if (snapshot.empty) break;
19626
- for (const d of snapshot.docs) {
19627
- const technology = { id: d.id, ...d.data() };
19628
- const productNames = await this.getProductNamesForTechnology(technology.id);
19629
- rows.push(this.technologyToCsvRow(technology, productNames));
19630
- }
19631
- cursor = snapshot.docs[snapshot.docs.length - 1];
19632
- if (snapshot.size < PAGE_SIZE) break;
19633
- }
19634
- const csvBody = rows.join("\r\n");
19635
- return includeBom ? "\uFEFF" + csvBody : csvBody;
19636
- }
19637
- /**
19638
- * Gets product names from the technology's product subcollection
19639
- */
19640
- async getProductNamesForTechnology(technologyId) {
19641
- try {
19642
- const productsRef = (0, import_firestore61.collection)(this.db, TECHNOLOGIES_COLLECTION, technologyId, PRODUCTS_COLLECTION);
19643
- const q = (0, import_firestore61.query)(productsRef, (0, import_firestore61.where)("isActive", "==", true));
19644
- const snapshot = await (0, import_firestore61.getDocs)(q);
19645
- return snapshot.docs.map((doc44) => {
19646
- const product = doc44.data();
19647
- return product.name || "";
19648
- }).filter((name) => name);
19649
- } catch (error) {
19650
- console.error(`Error fetching products for technology ${technologyId}:`, error);
19651
- return [];
19652
- }
19653
- }
19654
- technologyToCsvRow(technology, productNames = []) {
19655
- 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;
19656
- const values = [
19657
- (_a = technology.id) != null ? _a : "",
19658
- (_b = technology.name) != null ? _b : "",
19659
- (_c = technology.description) != null ? _c : "",
19660
- (_d = technology.family) != null ? _d : "",
19661
- (_e = technology.categoryId) != null ? _e : "",
19662
- (_f = technology.subcategoryId) != null ? _f : "",
19663
- (_g = technology.technicalDetails) != null ? _g : "",
19664
- (_j = (_i = (_h = technology.requirements) == null ? void 0 : _h.pre) == null ? void 0 : _i.map((r) => r.name).join(";")) != null ? _j : "",
19665
- (_m = (_l = (_k = technology.requirements) == null ? void 0 : _k.post) == null ? void 0 : _l.map((r) => r.name).join(";")) != null ? _m : "",
19666
- (_o = (_n = technology.blockingConditions) == null ? void 0 : _n.join(";")) != null ? _o : "",
19667
- (_q = (_p = technology.contraindications) == null ? void 0 : _p.map((c) => c.name).join(";")) != null ? _q : "",
19668
- (_s = (_r = technology.benefits) == null ? void 0 : _r.map((b) => b.name).join(";")) != null ? _s : "",
19669
- (_u = (_t = technology.certificationRequirement) == null ? void 0 : _t.minimumLevel) != null ? _u : "",
19670
- (_x = (_w = (_v = technology.certificationRequirement) == null ? void 0 : _v.requiredSpecialties) == null ? void 0 : _w.join(";")) != null ? _x : "",
19671
- (_z = (_y = technology.documentationTemplates) == null ? void 0 : _y.map((t) => t.templateId).join(";")) != null ? _z : "",
19672
- productNames.join(";"),
19673
- String((_A = technology.isActive) != null ? _A : "")
19674
- ];
19675
- return values.map((v) => this.formatCsvValue(v)).join(",");
19676
- }
19677
- formatCsvValue(value) {
19678
- const str = value === null || value === void 0 ? "" : String(value);
19679
- const escaped = str.replace(/"/g, '""');
19680
- return `"${escaped}"`;
19681
- }
19682
19258
  };
19683
19259
 
19684
19260
  // src/backoffice/services/product.service.ts
19685
19261
  var import_firestore62 = require("firebase/firestore");
19262
+
19263
+ // src/backoffice/types/product.types.ts
19264
+ var PRODUCTS_COLLECTION = "products";
19265
+
19266
+ // src/backoffice/services/product.service.ts
19686
19267
  var ProductService = class extends BaseService {
19687
19268
  /**
19688
- * Gets reference to top-level products collection (source of truth)
19689
- * @returns Firestore collection reference
19690
- */
19691
- getTopLevelProductsRef() {
19692
- return (0, import_firestore62.collection)(this.db, PRODUCTS_COLLECTION);
19693
- }
19694
- /**
19695
- * Gets reference to products collection under a technology (backward compatibility)
19269
+ * Gets reference to products collection under a technology
19696
19270
  * @param technologyId - ID of the technology
19697
19271
  * @returns Firestore collection reference
19698
19272
  */
@@ -19708,7 +19282,6 @@ var ProductService = class extends BaseService {
19708
19282
  ...product,
19709
19283
  brandId,
19710
19284
  technologyId,
19711
- // Required for old subcollection structure
19712
19285
  createdAt: now,
19713
19286
  updatedAt: now,
19714
19287
  isActive: true
@@ -19768,26 +19341,30 @@ var ProductService = class extends BaseService {
19768
19341
  }
19769
19342
  /**
19770
19343
  * Gets counts of active products grouped by category, subcategory, and technology.
19771
- * Queries technology subcollections which have the legacy fields synced by Cloud Functions.
19344
+ * This uses a single collectionGroup query for efficiency.
19772
19345
  */
19773
19346
  async getProductCounts() {
19347
+ const q = (0, import_firestore62.query)((0, import_firestore62.collectionGroup)(this.db, PRODUCTS_COLLECTION), (0, import_firestore62.where)("isActive", "==", true));
19348
+ const snapshot = await (0, import_firestore62.getDocs)(q);
19774
19349
  const counts = {
19775
19350
  byCategory: {},
19776
19351
  bySubcategory: {},
19777
19352
  byTechnology: {}
19778
19353
  };
19779
- const q = (0, import_firestore62.query)((0, import_firestore62.collectionGroup)(this.db, PRODUCTS_COLLECTION), (0, import_firestore62.where)("isActive", "==", true));
19780
- const snapshot = await (0, import_firestore62.getDocs)(q);
19354
+ if (snapshot.empty) {
19355
+ return counts;
19356
+ }
19781
19357
  snapshot.docs.forEach((doc44) => {
19782
19358
  const product = doc44.data();
19783
- if (product.categoryId) {
19784
- counts.byCategory[product.categoryId] = (counts.byCategory[product.categoryId] || 0) + 1;
19359
+ const { categoryId, subcategoryId, technologyId } = product;
19360
+ if (categoryId) {
19361
+ counts.byCategory[categoryId] = (counts.byCategory[categoryId] || 0) + 1;
19785
19362
  }
19786
- if (product.subcategoryId) {
19787
- counts.bySubcategory[product.subcategoryId] = (counts.bySubcategory[product.subcategoryId] || 0) + 1;
19363
+ if (subcategoryId) {
19364
+ counts.bySubcategory[subcategoryId] = (counts.bySubcategory[subcategoryId] || 0) + 1;
19788
19365
  }
19789
- if (product.technologyId) {
19790
- counts.byTechnology[product.technologyId] = (counts.byTechnology[product.technologyId] || 0) + 1;
19366
+ if (technologyId) {
19367
+ counts.byTechnology[technologyId] = (counts.byTechnology[technologyId] || 0) + 1;
19791
19368
  }
19792
19369
  });
19793
19370
  return counts;
@@ -19866,241 +19443,6 @@ var ProductService = class extends BaseService {
19866
19443
  ...docSnap.data()
19867
19444
  };
19868
19445
  }
19869
- // ==========================================
19870
- // NEW METHODS: Top-level collection (preferred)
19871
- // ==========================================
19872
- /**
19873
- * Creates a new product in the top-level collection
19874
- */
19875
- async createTopLevel(brandId, product, technologyIds = []) {
19876
- const now = /* @__PURE__ */ new Date();
19877
- const newProduct = {
19878
- ...product,
19879
- brandId,
19880
- assignedTechnologyIds: technologyIds,
19881
- createdAt: now,
19882
- updatedAt: now,
19883
- isActive: true
19884
- };
19885
- const productRef = await (0, import_firestore62.addDoc)(this.getTopLevelProductsRef(), newProduct);
19886
- return { id: productRef.id, ...newProduct };
19887
- }
19888
- /**
19889
- * Gets all products from the top-level collection
19890
- */
19891
- async getAllTopLevel(options) {
19892
- const { rowsPerPage, lastVisible, brandId } = options;
19893
- const constraints = [(0, import_firestore62.where)("isActive", "==", true), (0, import_firestore62.orderBy)("name")];
19894
- if (brandId) {
19895
- constraints.push((0, import_firestore62.where)("brandId", "==", brandId));
19896
- }
19897
- if (lastVisible) {
19898
- constraints.push((0, import_firestore62.startAfter)(lastVisible));
19899
- }
19900
- constraints.push((0, import_firestore62.limit)(rowsPerPage));
19901
- const q = (0, import_firestore62.query)(this.getTopLevelProductsRef(), ...constraints);
19902
- const snapshot = await (0, import_firestore62.getDocs)(q);
19903
- const products = snapshot.docs.map(
19904
- (doc44) => ({
19905
- id: doc44.id,
19906
- ...doc44.data()
19907
- })
19908
- );
19909
- const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
19910
- return { products, lastVisible: newLastVisible };
19911
- }
19912
- /**
19913
- * Gets a product by ID from the top-level collection
19914
- */
19915
- async getByIdTopLevel(productId) {
19916
- const docRef = (0, import_firestore62.doc)(this.getTopLevelProductsRef(), productId);
19917
- const docSnap = await (0, import_firestore62.getDoc)(docRef);
19918
- if (!docSnap.exists()) return null;
19919
- return {
19920
- id: docSnap.id,
19921
- ...docSnap.data()
19922
- };
19923
- }
19924
- /**
19925
- * Updates a product in the top-level collection
19926
- */
19927
- async updateTopLevel(productId, product) {
19928
- const updateData = {
19929
- ...product,
19930
- updatedAt: /* @__PURE__ */ new Date()
19931
- };
19932
- const docRef = (0, import_firestore62.doc)(this.getTopLevelProductsRef(), productId);
19933
- await (0, import_firestore62.updateDoc)(docRef, updateData);
19934
- return this.getByIdTopLevel(productId);
19935
- }
19936
- /**
19937
- * Deletes a product from the top-level collection (soft delete)
19938
- */
19939
- async deleteTopLevel(productId) {
19940
- await this.updateTopLevel(productId, {
19941
- isActive: false
19942
- });
19943
- }
19944
- /**
19945
- * Assigns a product to a technology
19946
- */
19947
- async assignToTechnology(productId, technologyId) {
19948
- const docRef = (0, import_firestore62.doc)(this.getTopLevelProductsRef(), productId);
19949
- await (0, import_firestore62.updateDoc)(docRef, {
19950
- assignedTechnologyIds: (0, import_firestore62.arrayUnion)(technologyId),
19951
- updatedAt: /* @__PURE__ */ new Date()
19952
- });
19953
- }
19954
- /**
19955
- * Unassigns a product from a technology
19956
- */
19957
- async unassignFromTechnology(productId, technologyId) {
19958
- const docRef = (0, import_firestore62.doc)(this.getTopLevelProductsRef(), productId);
19959
- await (0, import_firestore62.updateDoc)(docRef, {
19960
- assignedTechnologyIds: (0, import_firestore62.arrayRemove)(technologyId),
19961
- updatedAt: /* @__PURE__ */ new Date()
19962
- });
19963
- }
19964
- /**
19965
- * Gets products assigned to a specific technology
19966
- */
19967
- async getAssignedProducts(technologyId) {
19968
- const q = (0, import_firestore62.query)(
19969
- this.getTopLevelProductsRef(),
19970
- (0, import_firestore62.where)("assignedTechnologyIds", "array-contains", technologyId),
19971
- (0, import_firestore62.where)("isActive", "==", true),
19972
- (0, import_firestore62.orderBy)("name")
19973
- );
19974
- const snapshot = await (0, import_firestore62.getDocs)(q);
19975
- return snapshot.docs.map(
19976
- (doc44) => ({
19977
- id: doc44.id,
19978
- ...doc44.data()
19979
- })
19980
- );
19981
- }
19982
- /**
19983
- * Gets products NOT assigned to a specific technology
19984
- */
19985
- async getUnassignedProducts(technologyId) {
19986
- const q = (0, import_firestore62.query)(
19987
- this.getTopLevelProductsRef(),
19988
- (0, import_firestore62.where)("isActive", "==", true),
19989
- (0, import_firestore62.orderBy)("name")
19990
- );
19991
- const snapshot = await (0, import_firestore62.getDocs)(q);
19992
- const allProducts = snapshot.docs.map(
19993
- (doc44) => ({
19994
- id: doc44.id,
19995
- ...doc44.data()
19996
- })
19997
- );
19998
- return allProducts.filter(
19999
- (product) => {
20000
- var _a;
20001
- return !((_a = product.assignedTechnologyIds) == null ? void 0 : _a.includes(technologyId));
20002
- }
20003
- );
20004
- }
20005
- /**
20006
- * Gets all products for a brand (from top-level collection)
20007
- */
20008
- async getByBrand(brandId) {
20009
- const q = (0, import_firestore62.query)(
20010
- this.getTopLevelProductsRef(),
20011
- (0, import_firestore62.where)("brandId", "==", brandId),
20012
- (0, import_firestore62.where)("isActive", "==", true),
20013
- (0, import_firestore62.orderBy)("name")
20014
- );
20015
- const snapshot = await (0, import_firestore62.getDocs)(q);
20016
- return snapshot.docs.map(
20017
- (doc44) => ({
20018
- id: doc44.id,
20019
- ...doc44.data()
20020
- })
20021
- );
20022
- }
20023
- /**
20024
- * Exports products to CSV string, suitable for Excel/Sheets.
20025
- * Includes headers and optional UTF-8 BOM.
20026
- * By default exports only active products (set includeInactive to true to export all).
20027
- */
20028
- async exportToCsv(options) {
20029
- var _a, _b;
20030
- const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
20031
- const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
20032
- const headers = [
20033
- "id",
20034
- "name",
20035
- "brandId",
20036
- "brandName",
20037
- "assignedTechnologyIds",
20038
- "description",
20039
- "technicalDetails",
20040
- "dosage",
20041
- "composition",
20042
- "indications",
20043
- "contraindications",
20044
- "warnings",
20045
- "isActive"
20046
- ];
20047
- const rows = [];
20048
- rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
20049
- const PAGE_SIZE = 1e3;
20050
- let cursor;
20051
- const constraints = [];
20052
- if (!includeInactive) {
20053
- constraints.push((0, import_firestore62.where)("isActive", "==", true));
20054
- }
20055
- constraints.push((0, import_firestore62.orderBy)("name"));
20056
- while (true) {
20057
- const queryConstraints = [...constraints, (0, import_firestore62.limit)(PAGE_SIZE)];
20058
- if (cursor) queryConstraints.push((0, import_firestore62.startAfter)(cursor));
20059
- const q = (0, import_firestore62.query)(this.getTopLevelProductsRef(), ...queryConstraints);
20060
- const snapshot = await (0, import_firestore62.getDocs)(q);
20061
- if (snapshot.empty) break;
20062
- for (const d of snapshot.docs) {
20063
- const product = { id: d.id, ...d.data() };
20064
- rows.push(this.productToCsvRow(product));
20065
- }
20066
- cursor = snapshot.docs[snapshot.docs.length - 1];
20067
- if (snapshot.size < PAGE_SIZE) break;
20068
- }
20069
- const csvBody = rows.join("\r\n");
20070
- return includeBom ? "\uFEFF" + csvBody : csvBody;
20071
- }
20072
- productToCsvRow(product) {
20073
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q;
20074
- const values = [
20075
- (_a = product.id) != null ? _a : "",
20076
- (_b = product.name) != null ? _b : "",
20077
- (_c = product.brandId) != null ? _c : "",
20078
- (_d = product.brandName) != null ? _d : "",
20079
- (_f = (_e = product.assignedTechnologyIds) == null ? void 0 : _e.join(";")) != null ? _f : "",
20080
- (_g = product.description) != null ? _g : "",
20081
- (_h = product.technicalDetails) != null ? _h : "",
20082
- (_i = product.dosage) != null ? _i : "",
20083
- (_j = product.composition) != null ? _j : "",
20084
- (_l = (_k = product.indications) == null ? void 0 : _k.join(";")) != null ? _l : "",
20085
- (_n = (_m = product.contraindications) == null ? void 0 : _m.map((c) => c.name).join(";")) != null ? _n : "",
20086
- (_p = (_o = product.warnings) == null ? void 0 : _o.join(";")) != null ? _p : "",
20087
- String((_q = product.isActive) != null ? _q : "")
20088
- ];
20089
- return values.map((v) => this.formatCsvValue(v)).join(",");
20090
- }
20091
- formatDateIso(value) {
20092
- if (value instanceof Date) return value.toISOString();
20093
- if (value && typeof value.toDate === "function") {
20094
- const d = value.toDate();
20095
- return d instanceof Date ? d.toISOString() : String(value);
20096
- }
20097
- return String(value != null ? value : "");
20098
- }
20099
- formatCsvValue(value) {
20100
- const str = value === null || value === void 0 ? "" : String(value);
20101
- const escaped = str.replace(/"/g, '""');
20102
- return `"${escaped}"`;
20103
- }
20104
19446
  };
20105
19447
 
20106
19448
  // src/backoffice/services/constants.service.ts
@@ -20333,66 +19675,6 @@ var ConstantsService = class extends BaseService {
20333
19675
  contraindications: (0, import_firestore63.arrayRemove)(toRemove)
20334
19676
  });
20335
19677
  }
20336
- // =================================================================
20337
- // CSV Export Methods
20338
- // =================================================================
20339
- /**
20340
- * Exports treatment benefits to CSV string, suitable for Excel/Sheets.
20341
- * Includes headers and optional UTF-8 BOM.
20342
- */
20343
- async exportBenefitsToCsv(options) {
20344
- var _a;
20345
- const includeBom = (_a = options == null ? void 0 : options.includeBom) != null ? _a : true;
20346
- const headers = ["id", "name", "description"];
20347
- const rows = [];
20348
- rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
20349
- const benefits = await this.getAllBenefitsForFilter();
20350
- for (const benefit of benefits) {
20351
- rows.push(this.benefitToCsvRow(benefit));
20352
- }
20353
- const csvBody = rows.join("\r\n");
20354
- return includeBom ? "\uFEFF" + csvBody : csvBody;
20355
- }
20356
- /**
20357
- * Exports contraindications to CSV string, suitable for Excel/Sheets.
20358
- * Includes headers and optional UTF-8 BOM.
20359
- */
20360
- async exportContraindicationsToCsv(options) {
20361
- var _a;
20362
- const includeBom = (_a = options == null ? void 0 : options.includeBom) != null ? _a : true;
20363
- const headers = ["id", "name", "description"];
20364
- const rows = [];
20365
- rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
20366
- const contraindications = await this.getAllContraindicationsForFilter();
20367
- for (const contraindication of contraindications) {
20368
- rows.push(this.contraindicationToCsvRow(contraindication));
20369
- }
20370
- const csvBody = rows.join("\r\n");
20371
- return includeBom ? "\uFEFF" + csvBody : csvBody;
20372
- }
20373
- benefitToCsvRow(benefit) {
20374
- var _a, _b, _c;
20375
- const values = [
20376
- (_a = benefit.id) != null ? _a : "",
20377
- (_b = benefit.name) != null ? _b : "",
20378
- (_c = benefit.description) != null ? _c : ""
20379
- ];
20380
- return values.map((v) => this.formatCsvValue(v)).join(",");
20381
- }
20382
- contraindicationToCsvRow(contraindication) {
20383
- var _a, _b, _c;
20384
- const values = [
20385
- (_a = contraindication.id) != null ? _a : "",
20386
- (_b = contraindication.name) != null ? _b : "",
20387
- (_c = contraindication.description) != null ? _c : ""
20388
- ];
20389
- return values.map((v) => this.formatCsvValue(v)).join(",");
20390
- }
20391
- formatCsvValue(value) {
20392
- const str = value === null || value === void 0 ? "" : String(value);
20393
- const escaped = str.replace(/"/g, '""');
20394
- return `"${escaped}"`;
20395
- }
20396
19678
  };
20397
19679
 
20398
19680
  // src/backoffice/types/static/contraindication.types.ts