UNPKG

whatsapp-crm-common

Version:

Componentes compartidos para servicios de WhatsApp CRM - Common utilities and types for WhatsApp CRM system

248 lines 11.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ContactRepository = void 0; const base_repository_1 = __importDefault(require("./base-repository")); class ContactRepository extends base_repository_1.default { /** * Guarda múltiples contactos en la base de datos en lotes */ async guardarContactosBulk(client, contactos, tenantId, agentId) { if (contactos.length === 0) return; // Número máximo de contactos por lote (cada contacto usa 7 parámetros incluyendo tenant_id y agent_id) const maxContactosPerBatch = 140; // ~1000 parámetros máximo // Procesar en lotes para evitar límites de parámetros en PostgreSQL for (let i = 0; i < contactos.length; i += maxContactosPerBatch) { const loteContactos = contactos.slice(i, i + maxContactosPerBatch); let valuesSql = []; let paramCounter = 1; let params = []; for (const contacto of loteContactos) { if (!contacto.id) continue; valuesSql.push(`($${paramCounter++}, $${paramCounter++}, $${paramCounter++}, $${paramCounter++}, $${paramCounter++}, $${paramCounter++}, $${paramCounter++})`); params.push(contacto.id, tenantId, agentId, contacto.name || null, contacto.notify || null, contacto.verifiedName || null, contacto.id?.split("@")[0] || null); } if (valuesSql.length === 0) continue; const query = ` INSERT INTO contacts (id, tenant_id, agent_id, name, notify, verified_name, phone_number) VALUES ${valuesSql.join(", ")} ON CONFLICT (id, tenant_id, agent_id) DO UPDATE SET name = COALESCE(EXCLUDED.name, contacts.name), notify = COALESCE(EXCLUDED.notify, contacts.notify), verified_name = COALESCE(EXCLUDED.verified_name, contacts.verified_name), phone_number = COALESCE(EXCLUDED.phone_number, contacts.phone_number), last_updated = CURRENT_TIMESTAMP `; console.log(`Insertando lote de ${loteContactos.length} contactos (${params.length} parámetros)`); await client.query(query, params); } } /** * Maneja relaciones entre chats y contactos */ async guardarChatContactRelationsBulk(client, relations, tenantId, agentId) { if (relations.length === 0) return; // 1. Verificar qué contactos necesitamos y cuáles ya existen en la BD const contactIds = [...new Set(relations.map((r) => r.contactId))]; const checkQuery = `SELECT id FROM contacts WHERE id = ANY($1::text[]) AND tenant_id = $2 AND agent_id = $3`; const existingContactsResult = await client.query(checkQuery, [ contactIds, tenantId, agentId, ]); const existingContactIds = new Set(existingContactsResult.rows.map((row) => row.id)); // 2. Identificar contactos faltantes const missingContactIds = contactIds.filter((id) => !existingContactIds.has(id)); // 3. Crear contactos faltantes si es necesario if (missingContactIds.length > 0) { console.log(`ADVERTENCIA: Se detectaron ${missingContactIds.length} contactos faltantes al crear relaciones chat-contacto`); console.log(`Contactos faltantes: ${missingContactIds.join(", ")}`); // Crear contactos placeholder para estos IDs faltantes const placeholderContacts = missingContactIds.map((id) => ({ id, name: id.split("@")[0], notify: id.split("@")[0], verifiedName: undefined, imgUrl: undefined, status: undefined, })); await this.guardarContactosBulk(client, placeholderContacts, tenantId, agentId); } // 4. Proceder con la inserción de relaciones ahora que todos los contactos existen let valuesSql = []; let paramCounter = 1; let params = []; for (const relation of relations) { valuesSql.push(`($${paramCounter++}, $${paramCounter++}, $${paramCounter++}, $${paramCounter++})`); params.push(relation.chatId, tenantId, agentId, relation.contactId); } const query = ` INSERT INTO chat_contact_relation (chat_id, tenant_id, agent_id, contact_id) VALUES ${valuesSql.join(", ")} ON CONFLICT (chat_id, contact_id, tenant_id, agent_id) DO NOTHING `; await client.query(query, params); } /** * Maneja participantes de grupos */ async guardarGroupParticipantsBulk(client, participants, tenantId, agentId) { if (participants.length === 0) return; // Verificar qué contactos necesitamos y cuáles ya existen en la BD const contactIds = [...new Set(participants.map((p) => p.contactId))]; const checkQuery = `SELECT id FROM contacts WHERE id = ANY($1::text[]) AND tenant_id = $2 AND agent_id = $3`; const existingContactsResult = await client.query(checkQuery, [ contactIds, tenantId, agentId, ]); const existingContactIds = new Set(existingContactsResult.rows.map((row) => row.id)); // Identificar contactos faltantes const missingContactIds = contactIds.filter((id) => !existingContactIds.has(id)); // Crear contactos faltantes si es necesario if (missingContactIds.length > 0) { console.log(`ADVERTENCIA: Se detectaron ${missingContactIds.length} contactos faltantes al crear participantes de grupo`); // Crear contactos placeholder para estos IDs faltantes const placeholderContacts = missingContactIds.map((id) => ({ id, name: id.split("@")[0], notify: id.split("@")[0], verifiedName: undefined, imgUrl: undefined, status: undefined, })); await this.guardarContactosBulk(client, placeholderContacts, tenantId, agentId); } // Ahora proceder con la inserción de participantes let valuesSql = []; let paramCounter = 1; let params = []; for (const participant of participants) { valuesSql.push(`($${paramCounter++}, $${paramCounter++}, $${paramCounter++}, $${paramCounter++}, $${paramCounter++})`); params.push(participant.groupId, tenantId, agentId, participant.contactId, false); } const query = ` INSERT INTO group_participants (group_id, tenant_id, agent_id, contact_id, is_admin) VALUES ${valuesSql.join(", ")} ON CONFLICT (group_id, contact_id, tenant_id, agent_id) DO NOTHING `; await client.query(query, params); } /** * Crea contactos placeholder para IDs faltantes */ async createPlaceholderContacts(client, contactIds, tenantId, agentId) { if (contactIds.length === 0) return; const placeholderContacts = contactIds.map((id) => ({ id, name: id.split("@")[0], notify: id.split("@")[0], verifiedName: undefined, imgUrl: undefined, status: undefined, })); await this.guardarContactosBulk(client, placeholderContacts, tenantId, agentId); } /** * Verifica si los contactos existen y crea placeholders para los faltantes */ async ensureContactsExist(client, contactIds, tenantId, agentId) { if (contactIds.length === 0) return; const checkQuery = `SELECT id FROM contacts WHERE id = ANY($1::text[]) AND tenant_id = $2 AND agent_id = $3`; const existingContactsResult = await client.query(checkQuery, [ contactIds, tenantId, agentId, ]); const existingContactIds = new Set(existingContactsResult.rows.map((row) => row.id)); // Identificar contactos faltantes const missingContactIds = contactIds.filter((id) => !existingContactIds.has(id)); if (missingContactIds.length > 0) { await this.createPlaceholderContacts(client, missingContactIds, tenantId, agentId); } } /** * Maneja la inserción o actualización de contactos */ async handleContactUpserts(contacts, tenantId, agentId) { if (contacts.length === 0) return; const client = await this.pool.connect(); try { await client.query("BEGIN"); await this.guardarContactosBulk(client, contacts, tenantId, agentId); await client.query("COMMIT"); } catch (error) { await client.query("ROLLBACK"); console.error("Error al procesar contactos:", error); } finally { client.release(); } } /** * Maneja actualizaciones de contactos */ async handleContactUpdates(tenantId, agentId, updates) { if (updates.length === 0) return; const client = await this.pool.connect(); try { await client.query("BEGIN"); for (const update of updates) { if (!update.id) continue; const updateFields = {}; // Actualizar campos según lo que venga en el update if (update.name !== undefined) updateFields.name = update.name; if (update.notify !== undefined) updateFields.notify = update.notify; if (update.verifiedName !== undefined) updateFields.verified_name = update.verifiedName; if (update.imgUrl !== undefined) updateFields.img_url = update.imgUrl; if (update.status !== undefined) updateFields.status = update.status; if (Object.keys(updateFields).length === 0) continue; // Construir la consulta de actualización const setClause = Object.keys(updateFields) .map((key, index) => `${key} = $${index + 4}`) .join(", "); const values = [ update.id, tenantId, agentId, ...Object.values(updateFields), ]; const updateQuery = ` UPDATE public.contacts SET ${setClause}, last_updated = CURRENT_TIMESTAMP WHERE id = $1 AND tenant_id = $2 AND agent_id = $3 `; await client.query(updateQuery, values); } await client.query("COMMIT"); } catch (error) { await client.query("ROLLBACK"); console.error("Error al actualizar contactos:", error); } finally { client.release(); } } } exports.ContactRepository = ContactRepository; //# sourceMappingURL=contact-repository.js.map