607 lines
20 KiB
TypeScript
607 lines
20 KiB
TypeScript
import { Product, City, PaymentMethod, Order, SupplierPost, StoreSettings, Supplier, MonitoredWhatsAppGroup, User, ProductColorVariant } from '@/types';
|
||
import { normalizeModelKey, groupProductsByModel } from './model-grouper';
|
||
import { generateAttractiveProductCopy } from './attractive-copy';
|
||
import { getProductSocialProof } from './social-proof';
|
||
|
||
export const INITIAL_SUPPLIERS: Supplier[] = [
|
||
{
|
||
"id": "sup-jovic",
|
||
"name": "💎 2️⃣ DIFUSIÓN JOVIC STORE COLOMBIA🍀",
|
||
"phone": "573167455877",
|
||
"contactPerson": "Asesor Jovic Store",
|
||
"address": "Centro / Bodega Cali",
|
||
"notes": "Proveedor oficial Nike, Jordan, Nocta y Vomero calidades Top.",
|
||
"isActive": true
|
||
},
|
||
{
|
||
"id": "sup-ccmio",
|
||
"name": "EXCLUSIVE CC MÍO 3B",
|
||
"phone": "573159876543",
|
||
"contactPerson": "Despachos CC MÍO 3B",
|
||
"address": "CC MÍO Local 3B, Cali",
|
||
"notes": "Especialista en Adidas, Coach, Timberland y Skatewear.",
|
||
"isActive": true
|
||
},
|
||
{
|
||
"id": "sup-central",
|
||
"name": "BODEGA CENTRAL MAYORISTA",
|
||
"phone": "573101234567",
|
||
"contactPerson": "Logística Despachos",
|
||
"address": "San Andresito / Cali",
|
||
"notes": "Despacho rápido en 24 horas para separación inmediata.",
|
||
"isActive": true
|
||
}
|
||
];
|
||
|
||
export const INITIAL_CITIES: City[] = [
|
||
{
|
||
id: 'city-cali',
|
||
name: 'Cali',
|
||
department: 'Valle del Cauca',
|
||
shippingCost: 0,
|
||
deliveryTime: 'Entrega en 24 Horas (Domicilio Local)',
|
||
allowsContraentrega: true,
|
||
firstExchangeFree: true,
|
||
exchangePolicyNote: '1er Cambio de Talla 100% GRATIS en Cali (posteriores con flete estándar)',
|
||
},
|
||
{
|
||
id: 'city-jamundi',
|
||
name: 'Jamundí',
|
||
department: 'Valle del Cauca',
|
||
shippingCost: 5000,
|
||
deliveryTime: 'Entrega en 24 Horas',
|
||
allowsContraentrega: true,
|
||
firstExchangeFree: true,
|
||
exchangePolicyNote: '1er Cambio de Talla 100% GRATIS en Jamundí (posteriores con flete estándar)',
|
||
},
|
||
{
|
||
id: 'city-palmira',
|
||
name: 'Palmira',
|
||
department: 'Valle del Cauca',
|
||
shippingCost: 7000,
|
||
deliveryTime: 'Entrega en 24 Horas',
|
||
allowsContraentrega: true,
|
||
firstExchangeFree: true,
|
||
exchangePolicyNote: '1er Cambio de Talla 100% GRATIS en Palmira (posteriores con flete estándar)',
|
||
},
|
||
{
|
||
id: 'city-yumbo',
|
||
name: 'Yumbo',
|
||
department: 'Valle del Cauca',
|
||
shippingCost: 5000,
|
||
deliveryTime: 'Entrega en 24 Horas',
|
||
allowsContraentrega: true,
|
||
firstExchangeFree: true,
|
||
exchangePolicyNote: '1er Cambio de Talla 100% GRATIS en Yumbo (posteriores con flete estándar)',
|
||
},
|
||
{
|
||
id: 'city-bogota',
|
||
name: 'Bogotá D.C.',
|
||
department: 'Cundinamarca',
|
||
shippingCost: 12000,
|
||
deliveryTime: '2 a 3 días hábiles',
|
||
allowsContraentrega: true,
|
||
firstExchangeFree: false,
|
||
exchangePolicyNote: 'Cambio de talla garantizado (flete logístico asumido por el cliente)',
|
||
},
|
||
{
|
||
id: 'city-medellin',
|
||
name: 'Medellín',
|
||
department: 'Antioquia',
|
||
shippingCost: 12000,
|
||
deliveryTime: '2 a 3 días hábiles',
|
||
allowsContraentrega: true,
|
||
firstExchangeFree: false,
|
||
exchangePolicyNote: 'Cambio de talla garantizado (flete logístico asumido por el cliente)',
|
||
},
|
||
{
|
||
id: 'city-barranquilla',
|
||
name: 'Barranquilla',
|
||
department: 'Atlántico',
|
||
shippingCost: 14000,
|
||
deliveryTime: '3 a 4 días hábiles',
|
||
allowsContraentrega: true,
|
||
firstExchangeFree: false,
|
||
exchangePolicyNote: 'Cambio de talla garantizado (flete logístico asumido por el cliente)',
|
||
},
|
||
{
|
||
id: 'city-nacional',
|
||
name: 'Otras Ciudades / Resto del País',
|
||
department: 'Colombia',
|
||
shippingCost: 15000,
|
||
deliveryTime: '2 a 5 días hábiles',
|
||
allowsContraentrega: false,
|
||
firstExchangeFree: false,
|
||
exchangePolicyNote: 'Cambio de talla garantizado (flete nacional asumido por el cliente)',
|
||
},
|
||
];
|
||
|
||
// Métodos de Pago configurables por el Admin
|
||
export const INITIAL_PAYMENT_METHODS: PaymentMethod[] = [
|
||
{
|
||
id: 'pay-bold-link',
|
||
name: 'Bold.co • PSE, Tarjetas Débito/Crédito, Nequi o QR (Oficial)',
|
||
type: 'bold',
|
||
description: 'Paga de forma rápida y 100% segura con tu cuenta Bold. Acepta PSE, Tarjetas Visa/Mastercard y QR de cualquier banco.',
|
||
requiresDeposit: false,
|
||
depositAmount: 0,
|
||
boldPaymentLink: 'https://bold.co/p/carlos-lasso',
|
||
accountInfo: 'Pasarela Oficial Bold.co • PSE / Tarjetas / QR',
|
||
isActive: true,
|
||
},
|
||
{
|
||
id: 'pay-deposit-10k',
|
||
name: 'Separación con $10.000 + Saldo Contraentrega',
|
||
type: 'contraentrega',
|
||
description: 'Asegura tu talla con solo $10.000 COP vía Bold/Nequi/Daviplata y cancelas el saldo restante en efectivo o transferencia cuando el mensajero llegue a tu casa.',
|
||
requiresDeposit: true,
|
||
depositAmount: 10000,
|
||
accountInfo: 'Nequi / Daviplata Oficial: 316 745 5877 (Carlos Andrés Lasso)',
|
||
boldPaymentLink: 'https://bold.co/p/carlos-lasso',
|
||
isActive: true,
|
||
},
|
||
{
|
||
id: 'pay-cod-full',
|
||
name: 'Pago 100% Contraentrega (Cali y Alrededores)',
|
||
type: 'contraentrega',
|
||
description: 'Pagas el 100% del valor total al recibir en tu puerta (válido para entregas urbanas en 24 horas).',
|
||
requiresDeposit: false,
|
||
depositAmount: 0,
|
||
isActive: true,
|
||
},
|
||
{
|
||
id: 'pay-nequi-full',
|
||
name: 'Transferencia Total Anticipada Nequi / Daviplata',
|
||
type: 'nequi',
|
||
description: 'Paga el 100% anticipado y recibe prioridad de despacho express en 24 horas.',
|
||
requiresDeposit: false,
|
||
depositAmount: 0,
|
||
accountInfo: 'Nequi / Daviplata: 316 745 5877 (Carlos Andrés Lasso)',
|
||
isActive: true,
|
||
},
|
||
];
|
||
|
||
export const INITIAL_PRODUCTS: Product[] = [];
|
||
|
||
export const INITIAL_SUPPLIER_POSTS: SupplierPost[] = [];
|
||
|
||
export const INITIAL_ORDERS: Order[] = [];
|
||
|
||
export const INITIAL_USERS: User[] = [
|
||
{
|
||
id: 'usr-admin-carlos',
|
||
name: 'Carlos Lasso',
|
||
email: 'carlos.andres.lasso@gmail.com',
|
||
phone: '573167455877',
|
||
city: 'Cali',
|
||
address: 'Sede Principal / Cali',
|
||
role: 'admin',
|
||
twoFactorEnabled: false,
|
||
isActive: true,
|
||
createdAt: '2026-01-01',
|
||
totalOrders: 0,
|
||
},
|
||
];
|
||
|
||
export const INITIAL_MONITORED_GROUPS: MonitoredWhatsAppGroup[] = [];
|
||
|
||
class DataService {
|
||
private products: Product[] = [...INITIAL_PRODUCTS];
|
||
private cities: City[] = [...INITIAL_CITIES];
|
||
private paymentMethods: PaymentMethod[] = [...INITIAL_PAYMENT_METHODS];
|
||
private suppliers: Supplier[] = [...INITIAL_SUPPLIERS];
|
||
private orders: Order[] = [...INITIAL_ORDERS];
|
||
private supplierPosts: SupplierPost[] = [...INITIAL_SUPPLIER_POSTS];
|
||
|
||
getProducts(): Product[] {
|
||
return groupProductsByModel(this.products);
|
||
}
|
||
|
||
getProductById(id: string): Product | undefined {
|
||
const allGrouped = groupProductsByModel(this.products);
|
||
return allGrouped.find((p) => p.id === id || p.colors?.some((c) => c.id === id));
|
||
}
|
||
|
||
addProduct(product: Product): Product {
|
||
const brand = product.brand || 'Sneakers';
|
||
const baseModelName = product.modelName || product.name;
|
||
const modelKey = normalizeModelKey(baseModelName, brand);
|
||
|
||
// Buscar si ya existe un producto del mismo modelo
|
||
const existing = this.products.find(
|
||
(p) => normalizeModelKey(p.modelName || p.name, p.brand) === modelKey
|
||
);
|
||
|
||
if (existing) {
|
||
const existingColors: ProductColorVariant[] =
|
||
Array.isArray(existing.colors) && existing.colors.length > 0
|
||
? existing.colors
|
||
: [
|
||
{
|
||
id: `color-${existing.id}-1`,
|
||
colorName: existing.colorName || 'Estilo #1',
|
||
image: existing.images[0],
|
||
images: existing.images,
|
||
sizes: existing.sizes,
|
||
inStock: existing.inStock !== false,
|
||
},
|
||
];
|
||
|
||
const newImage = product.images[0] || (product.colors && product.colors[0]?.image);
|
||
const isDuplicate = existingColors.some(
|
||
(c) => c.image === newImage || (product.colorName && c.colorName.toLowerCase() === product.colorName.toLowerCase())
|
||
);
|
||
|
||
if (!isDuplicate && newImage) {
|
||
existingColors.push({
|
||
id: `color-${Date.now()}-${existingColors.length + 1}`,
|
||
colorName: product.colorName || `Estilo #${existingColors.length + 1}`,
|
||
image: newImage,
|
||
images: product.images.length > 0 ? product.images : [newImage],
|
||
sizes: product.sizes && product.sizes.length > 0 ? product.sizes : existing.sizes,
|
||
inStock: product.inStock !== false,
|
||
});
|
||
}
|
||
|
||
existing.colors = existingColors;
|
||
existing.images = Array.from(new Set([...existing.images, ...product.images]));
|
||
existing.sizes = Array.from(new Set([...existing.sizes, ...(product.sizes || [])])).sort(
|
||
(a, b) => parseInt(a, 10) - parseInt(b, 10)
|
||
);
|
||
if (!existing.description || existing.description.length < 50) {
|
||
existing.description = generateAttractiveProductCopy(existing);
|
||
}
|
||
return existing;
|
||
}
|
||
|
||
// Si es un modelo nuevo
|
||
const proof = getProductSocialProof(product);
|
||
const initialColors: ProductColorVariant[] =
|
||
Array.isArray(product.colors) && product.colors.length > 0
|
||
? product.colors
|
||
: [
|
||
{
|
||
id: `color-${Date.now()}-1`,
|
||
colorName: product.colorName || 'Estilo Principal',
|
||
image: product.images[0],
|
||
images: product.images,
|
||
sizes: product.sizes || ['37', '38', '39', '40', '41', '42', '43'],
|
||
inStock: product.inStock !== false,
|
||
},
|
||
];
|
||
|
||
const formattedProduct: Product = {
|
||
...product,
|
||
modelName: baseModelName,
|
||
colors: initialColors,
|
||
description: product.description || generateAttractiveProductCopy(product),
|
||
rating: product.rating || proof.rating,
|
||
reviewsCount: product.reviewsCount || proof.reviewsCount,
|
||
salesCount: product.salesCount || proof.salesCount,
|
||
originBadge: product.originBadge || proof.badge,
|
||
};
|
||
|
||
this.products.unshift(formattedProduct);
|
||
return formattedProduct;
|
||
}
|
||
|
||
updateProduct(id: string, updates: Partial<Product>): Product | null {
|
||
const idx = this.products.findIndex((p) => p.id === id);
|
||
if (idx === -1) return null;
|
||
this.products[idx] = { ...this.products[idx], ...updates };
|
||
return this.products[idx];
|
||
}
|
||
|
||
deleteProduct(id: string): boolean {
|
||
const initialLen = this.products.length;
|
||
this.products = this.products.filter((p) => p.id !== id);
|
||
return this.products.length < initialLen;
|
||
}
|
||
|
||
getSuppliers(includeInactive: boolean = true): Supplier[] {
|
||
if (includeInactive) return this.suppliers;
|
||
return this.suppliers.filter((s) => s.isActive !== false);
|
||
}
|
||
|
||
getSupplierById(id: string): Supplier | undefined {
|
||
return this.suppliers.find((s) => s.id === id);
|
||
}
|
||
|
||
addSupplier(supplier: Partial<Supplier>): Supplier {
|
||
const newSupplier: Supplier = {
|
||
id: supplier.id || `sup-${Date.now()}`,
|
||
name: supplier.name || 'Nuevo Proveedor',
|
||
phone: supplier.phone || '573167455877',
|
||
contactPerson: supplier.contactPerson || '',
|
||
address: supplier.address || '',
|
||
notes: supplier.notes || '',
|
||
isActive: supplier.isActive !== undefined ? supplier.isActive : true,
|
||
};
|
||
this.suppliers.push(newSupplier);
|
||
return newSupplier;
|
||
}
|
||
|
||
updateSupplier(id: string, updates: Partial<Supplier>): Supplier | null {
|
||
const idx = this.suppliers.findIndex((s) => s.id === id);
|
||
if (idx === -1) return null;
|
||
this.suppliers[idx] = { ...this.suppliers[idx], ...updates };
|
||
return this.suppliers[idx];
|
||
}
|
||
|
||
deleteSupplier(id: string): boolean {
|
||
// Soft delete (desactivación segura para preservar 100% el historial de pedidos y productos)
|
||
const supplier = this.suppliers.find((s) => s.id === id);
|
||
if (!supplier) return false;
|
||
supplier.isActive = false;
|
||
return true;
|
||
}
|
||
|
||
getCities(): City[] {
|
||
return this.cities;
|
||
}
|
||
|
||
getCityById(id: string): City | undefined {
|
||
return this.cities.find((c) => c.id === id);
|
||
}
|
||
|
||
addCity(city: City): City {
|
||
this.cities.push(city);
|
||
return city;
|
||
}
|
||
|
||
updateCity(id: string, updates: Partial<City>): City | null {
|
||
const idx = this.cities.findIndex((c) => c.id === id);
|
||
if (idx === -1) return null;
|
||
this.cities[idx] = { ...this.cities[idx], ...updates };
|
||
return this.cities[idx];
|
||
}
|
||
|
||
getPaymentMethods(): PaymentMethod[] {
|
||
return this.paymentMethods;
|
||
}
|
||
|
||
updatePaymentMethod(id: string, updates: Partial<PaymentMethod>): PaymentMethod | null {
|
||
const idx = this.paymentMethods.findIndex((p) => p.id === id);
|
||
if (idx === -1) return null;
|
||
this.paymentMethods[idx] = { ...this.paymentMethods[idx], ...updates };
|
||
return this.paymentMethods[idx];
|
||
}
|
||
|
||
getOrders(): Order[] {
|
||
return this.orders;
|
||
}
|
||
|
||
getOrderById(id: string): Order | undefined {
|
||
return this.orders.find((o) => o.id === id);
|
||
}
|
||
|
||
addOrder(order: Order): Order {
|
||
this.orders.unshift(order);
|
||
return order;
|
||
}
|
||
|
||
createOrder(orderData: Partial<Order>): Order {
|
||
const newOrder: Order = {
|
||
id: orderData.id || `ord-${Date.now()}`,
|
||
customerName: orderData.customerName || 'Cliente',
|
||
customerPhone: orderData.customerPhone || '',
|
||
address: orderData.address || '',
|
||
cityId: orderData.cityId || 'city-cali',
|
||
cityName: orderData.cityName || 'Cali',
|
||
items: orderData.items || [],
|
||
subtotal: orderData.subtotal || 0,
|
||
shippingCost: orderData.shippingCost || 0,
|
||
totalAmount: orderData.totalAmount || 0,
|
||
depositAmount: orderData.depositAmount !== undefined ? orderData.depositAmount : 10000,
|
||
balanceDue: orderData.balanceDue !== undefined ? orderData.balanceDue : ((orderData.totalAmount || 0) - (orderData.depositAmount !== undefined ? orderData.depositAmount : 10000)),
|
||
paymentMethodId: orderData.paymentMethodId || 'pay-contraentrega',
|
||
paymentMethodName: orderData.paymentMethodName || 'Contraentrega',
|
||
status: orderData.status || 'pending',
|
||
notes: orderData.notes,
|
||
createdAt: orderData.createdAt || new Date().toISOString(),
|
||
};
|
||
this.orders.unshift(newOrder);
|
||
return newOrder;
|
||
}
|
||
|
||
updateOrder(id: string, updates: Partial<Order>): Order | null {
|
||
const idx = this.orders.findIndex((o) => o.id === id);
|
||
if (idx === -1) return null;
|
||
|
||
const current = this.orders[idx];
|
||
const total = updates.totalAmount !== undefined ? updates.totalAmount : current.totalAmount;
|
||
const deposit = updates.depositAmount !== undefined ? updates.depositAmount : current.depositAmount;
|
||
const balanceDue = updates.balanceDue !== undefined ? updates.balanceDue : (total - deposit);
|
||
|
||
this.orders[idx] = {
|
||
...current,
|
||
...updates,
|
||
totalAmount: total,
|
||
depositAmount: deposit,
|
||
balanceDue,
|
||
};
|
||
return this.orders[idx];
|
||
}
|
||
|
||
deleteOrder(id: string): boolean {
|
||
const initialLen = this.orders.length;
|
||
this.orders = this.orders.filter((o) => o.id !== id);
|
||
return this.orders.length < initialLen;
|
||
}
|
||
|
||
updateOrderStatus(orderId: string, status: Order['status'], rejectionReason?: string): Order | null {
|
||
const order = this.orders.find((o) => o.id === orderId);
|
||
if (!order) return null;
|
||
order.status = status;
|
||
if (rejectionReason) {
|
||
order.rejectionReason = rejectionReason;
|
||
}
|
||
|
||
// Si el pedido es rechazado por el cliente, activar obligatoriedad de cobro de flete/garantía para futuros pedidos
|
||
if (status === 'rejected') {
|
||
const cleanPhone = (order.customerPhone || '').replace(/\D/g, '');
|
||
let user = this.users.find(
|
||
(u) => (order.userId && u.id === order.userId) || u.phone.replace(/\D/g, '') === cleanPhone
|
||
);
|
||
|
||
if (user) {
|
||
user.requiresShippingDeposit = true;
|
||
user.rejectedOrdersCount = (user.rejectedOrdersCount || 0) + 1;
|
||
user.depositReason = `Garantía activada por pedido #${order.id} rechazado en entrega (${rejectionReason || 'No recibido en domicilio'})`;
|
||
} else if (cleanPhone) {
|
||
this.addUser({
|
||
id: `usr-risk-${Date.now()}`,
|
||
name: order.customerName,
|
||
email: `${cleanPhone}@cliente.dripzone.co`,
|
||
phone: cleanPhone,
|
||
city: order.cityName,
|
||
address: order.address,
|
||
role: 'customer',
|
||
phoneVerified: false,
|
||
requiresShippingDeposit: true,
|
||
depositReason: `Garantía activada por pedido #${order.id} rechazado en entrega`,
|
||
rejectedOrdersCount: 1,
|
||
isActive: true,
|
||
createdAt: new Date().toISOString().split('T')[0],
|
||
totalOrders: 1,
|
||
});
|
||
}
|
||
}
|
||
|
||
return order;
|
||
}
|
||
|
||
setUserShippingDeposit(userIdOrPhone: string, requiresDeposit: boolean, reason?: string): User | null {
|
||
const clean = userIdOrPhone.trim();
|
||
const cleanPhone = clean.replace(/\D/g, '');
|
||
const user = this.users.find(
|
||
(u) => u.id === clean || u.phone.replace(/\D/g, '') === cleanPhone || u.email.toLowerCase() === clean.toLowerCase()
|
||
);
|
||
if (!user) return null;
|
||
user.requiresShippingDeposit = requiresDeposit;
|
||
if (reason !== undefined) {
|
||
user.depositReason = reason;
|
||
}
|
||
return user;
|
||
}
|
||
|
||
getSupplierPosts(): SupplierPost[] {
|
||
return this.supplierPosts;
|
||
}
|
||
|
||
addSupplierPost(post: SupplierPost): SupplierPost {
|
||
this.supplierPosts.unshift(post);
|
||
return post;
|
||
}
|
||
|
||
saveSupplierPosts(posts: SupplierPost[]): SupplierPost[] {
|
||
posts.forEach((newP) => {
|
||
const exists = this.supplierPosts.some(
|
||
(p) => p.id === newP.id || (p.rawMessage && p.rawMessage === newP.rawMessage && (p as any).channelJid === (newP as any).channelJid)
|
||
);
|
||
if (!exists) {
|
||
this.supplierPosts.unshift(newP);
|
||
}
|
||
});
|
||
return this.supplierPosts;
|
||
}
|
||
|
||
updateSupplierPostStatus(id: string, status: SupplierPost['status']): boolean {
|
||
const post = this.supplierPosts.find((p) => p.id === id);
|
||
if (!post) return false;
|
||
post.status = status;
|
||
return true;
|
||
}
|
||
|
||
// Configuración de la Tienda
|
||
private settings: StoreSettings = {
|
||
storeName: 'DripZone',
|
||
slogan: 'Luxury Streetwear & Collectibles',
|
||
email: 'carlos.andres.lasso@gmail.com',
|
||
phone: '3167455877',
|
||
whatsappNumber: '573167455877',
|
||
instagramHandle: '@dripzone.co',
|
||
address: 'Bodega Central / Cali',
|
||
city: 'Cali, Colombia',
|
||
nequiAccount: '316 745 5877',
|
||
daviplataAccount: '316 745 5877',
|
||
accountHolder: 'Carlos Andrés Lasso',
|
||
boldPaymentLink: 'https://bold.co/p/carlos-lasso',
|
||
boldApiKey: '',
|
||
boldMerchantId: '',
|
||
boldQrUrl: '',
|
||
boldEnabled: true,
|
||
defaultProfitMargin: 35,
|
||
defaultDepositAmount: 10000,
|
||
};
|
||
|
||
getSettings(): StoreSettings {
|
||
return this.settings;
|
||
}
|
||
|
||
updateSettings(updates: Partial<StoreSettings>): StoreSettings {
|
||
this.settings = { ...this.settings, ...updates };
|
||
return this.settings;
|
||
}
|
||
|
||
// Control de Usuarios
|
||
private users: User[] = [...INITIAL_USERS];
|
||
|
||
getUsers(): User[] {
|
||
return this.users;
|
||
}
|
||
|
||
getUserById(id: string): User | undefined {
|
||
return this.users.find((u) => u.id === id);
|
||
}
|
||
|
||
addUser(user: User): User {
|
||
this.users.unshift(user);
|
||
return user;
|
||
}
|
||
|
||
updateUser(id: string, updates: Partial<User>): User | null {
|
||
const idx = this.users.findIndex((u) => u.id === id);
|
||
if (idx === -1) return null;
|
||
this.users[idx] = { ...this.users[idx], ...updates };
|
||
return this.users[idx];
|
||
}
|
||
|
||
deleteUser(id: string): boolean {
|
||
const idx = this.users.findIndex((u) => u.id === id);
|
||
if (idx === -1) return false;
|
||
this.users.splice(idx, 1);
|
||
return true;
|
||
}
|
||
|
||
authenticate(emailOrPhone: string): User | null {
|
||
const q = emailOrPhone.trim().toLowerCase();
|
||
const user = this.users.find(
|
||
(u) => u.email.toLowerCase() === q || u.phone === q || u.phone.includes(q)
|
||
);
|
||
return user || null;
|
||
}
|
||
|
||
private monitoredGroups: MonitoredWhatsAppGroup[] = [...INITIAL_MONITORED_GROUPS];
|
||
|
||
getMonitoredGroups(): MonitoredWhatsAppGroup[] {
|
||
return this.monitoredGroups;
|
||
}
|
||
|
||
saveMonitoredGroups(groups: MonitoredWhatsAppGroup[]): MonitoredWhatsAppGroup[] {
|
||
this.monitoredGroups = groups;
|
||
return this.monitoredGroups;
|
||
}
|
||
|
||
toggleGroupMonitoring(idOrJid: string, isMonitored: boolean): MonitoredWhatsAppGroup | null {
|
||
const grp = this.monitoredGroups.find((g) => g.id === idOrJid || g.jid === idOrJid);
|
||
if (grp) {
|
||
grp.isMonitored = isMonitored;
|
||
return grp;
|
||
}
|
||
return null;
|
||
}
|
||
}
|
||
|
||
export const dataService = new DataService();
|