|  |  |  | @ -80,39 +80,14 @@ export class GroupSyncService { | 
		
	
		
			
				|  |  |  |  | 		Metrics.inc('sync_runs_total'); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 		try { | 
		
	
		
			
				|  |  |  |  | 			const communityId = process.env.WHATSAPP_COMMUNITY_ID; | 
		
	
		
			
				|  |  |  |  | 			if (!communityId) { | 
		
	
		
			
				|  |  |  |  | 				console.log('ℹ️ WHATSAPP_COMMUNITY_ID no definido - mostrando todas las comunidades'); | 
		
	
		
			
				|  |  |  |  | 				const groups = await this.fetchGroupsFromAPI(); | 
		
	
		
			
				|  |  |  |  | 				const communities = groups.filter(g => g.linkedParent); | 
		
	
		
			
				|  |  |  |  | 				// Intento best-effort de rellenar labels faltantes en allowed_groups usando la lista completa
 | 
		
	
		
			
				|  |  |  |  | 				try { (AllowedGroups as any).dbInstance = this.dbInstance; this.fillMissingAllowedGroupLabels(groups); } catch {} | 
		
	
		
			
				|  |  |  |  | 				return { added: 0, updated: 0 }; // No sync when just listing
 | 
		
	
		
			
				|  |  |  |  | 			} | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 			const groups = await this.fetchGroupsFromAPI(); | 
		
	
		
			
				|  |  |  |  | 			console.log('ℹ️ Grupos crudos de la API:', JSON.stringify(groups, null, 2)); | 
		
	
		
			
				|  |  |  |  | 			 | 
		
	
		
			
				|  |  |  |  | 			const communityGroups = groups.filter((group) => { | 
		
	
		
			
				|  |  |  |  | 				const matches = group.linkedParent === communityId; | 
		
	
		
			
				|  |  |  |  | 				console.log(`ℹ️ Grupo ${group.id} (${group.subject}):`, { | 
		
	
		
			
				|  |  |  |  | 					linkedParent: group.linkedParent, | 
		
	
		
			
				|  |  |  |  | 					matchesCommunity: matches, | 
		
	
		
			
				|  |  |  |  | 					isCommunityItself: group.id === communityId | 
		
	
		
			
				|  |  |  |  | 				}); | 
		
	
		
			
				|  |  |  |  | 				return matches; | 
		
	
		
			
				|  |  |  |  | 			}); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 			console.log('ℹ️ Grupos que pasaron el filtro:', communityGroups.map(g => ({ | 
		
	
		
			
				|  |  |  |  | 				id: g.id, | 
		
	
		
			
				|  |  |  |  | 				name: g.subject, | 
		
	
		
			
				|  |  |  |  | 				parent: g.linkedParent | 
		
	
		
			
				|  |  |  |  | 			}))); | 
		
	
		
			
				|  |  |  |  | 			console.log('ℹ️ Sin filtrar por comunidad (modo multicomunidad). Total grupos:', groups.length); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 			const dbGroupsBefore = this.dbInstance.prepare('SELECT id, active FROM groups').all(); | 
		
	
		
			
				|  |  |  |  | 			console.log('ℹ️ Grupos en DB antes de upsert:', dbGroupsBefore); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 			const result = await this.upsertGroups(communityGroups); | 
		
	
		
			
				|  |  |  |  | 			const result = await this.upsertGroups(groups); | 
		
	
		
			
				|  |  |  |  | 			 | 
		
	
		
			
				|  |  |  |  | 			const dbGroupsAfter = this.dbInstance.prepare('SELECT id, active FROM groups').all(); | 
		
	
		
			
				|  |  |  |  | 			console.log('ℹ️ Grupos en DB después de upsert:', dbGroupsAfter); | 
		
	
	
		
			
				
					|  |  |  | @ -368,14 +343,14 @@ export class GroupSyncService { | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 				if (existing) { | 
		
	
		
			
				|  |  |  |  | 					const updateResult = this.dbInstance.prepare( | 
		
	
		
			
				|  |  |  |  | 						'UPDATE groups SET name = ?, active = TRUE, last_verified = CURRENT_TIMESTAMP WHERE id = ?' | 
		
	
		
			
				|  |  |  |  | 					).run(group.subject, group.id); | 
		
	
		
			
				|  |  |  |  | 						'UPDATE groups SET name = ?, community_id = COALESCE(?, community_id), active = TRUE, last_verified = CURRENT_TIMESTAMP WHERE id = ?' | 
		
	
		
			
				|  |  |  |  | 					).run(group.subject, group.linkedParent || null, group.id); | 
		
	
		
			
				|  |  |  |  | 					console.log('Updated group:', group.id, 'result:', updateResult); | 
		
	
		
			
				|  |  |  |  | 					updated++; | 
		
	
		
			
				|  |  |  |  | 				} else { | 
		
	
		
			
				|  |  |  |  | 					const insertResult = this.dbInstance.prepare( | 
		
	
		
			
				|  |  |  |  | 						'INSERT INTO groups (id, community_id, name, active) VALUES (?, ?, ?, TRUE)' | 
		
	
		
			
				|  |  |  |  | 					).run(group.id, process.env.WHATSAPP_COMMUNITY_ID, group.subject); | 
		
	
		
			
				|  |  |  |  | 					).run(group.id, group.linkedParent || null, group.subject); | 
		
	
		
			
				|  |  |  |  | 					console.log('Added group:', group.id, 'result:', insertResult); | 
		
	
		
			
				|  |  |  |  | 					added++; | 
		
	
		
			
				|  |  |  |  | 				} | 
		
	
	
		
			
				
					|  |  |  | @ -860,6 +835,54 @@ export class GroupSyncService { | 
		
	
		
			
				|  |  |  |  | 		return { created, reactivated, updatedName }; | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 	/** | 
		
	
		
			
				|  |  |  |  | 	 * Asegura tener el nombre/label de un grupo (cache/DB/API) y lo persiste tanto en groups como en allowed_groups. | 
		
	
		
			
				|  |  |  |  | 	 * Devuelve el nombre si se pudo resolver, o null en caso contrario. | 
		
	
		
			
				|  |  |  |  | 	 */ | 
		
	
		
			
				|  |  |  |  | 	public static async ensureGroupLabelAndName(groupId: string): Promise<string | null> { | 
		
	
		
			
				|  |  |  |  | 		try { | 
		
	
		
			
				|  |  |  |  | 			if (!groupId) return null; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 			// 1) Cache en memoria
 | 
		
	
		
			
				|  |  |  |  | 			const cached = this.activeGroupsCache.get(groupId); | 
		
	
		
			
				|  |  |  |  | 			if (cached && cached.trim()) { | 
		
	
		
			
				|  |  |  |  | 				try { this.ensureGroupExists(groupId, cached); } catch {} | 
		
	
		
			
				|  |  |  |  | 				try { (AllowedGroups as any).dbInstance = this.dbInstance; AllowedGroups.upsertPending(groupId, cached, null); } catch {} | 
		
	
		
			
				|  |  |  |  | 				this.cacheActiveGroups(); | 
		
	
		
			
				|  |  |  |  | 				return cached; | 
		
	
		
			
				|  |  |  |  | 			} | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 			// 2) DB (tabla groups)
 | 
		
	
		
			
				|  |  |  |  | 			try { | 
		
	
		
			
				|  |  |  |  | 				const row = this.dbInstance.prepare('SELECT name FROM groups WHERE id = ?').get(groupId) as any; | 
		
	
		
			
				|  |  |  |  | 				const name = row?.name ? String(row.name).trim() : ''; | 
		
	
		
			
				|  |  |  |  | 				if (name) { | 
		
	
		
			
				|  |  |  |  | 					try { this.ensureGroupExists(groupId, name); } catch {} | 
		
	
		
			
				|  |  |  |  | 					try { (AllowedGroups as any).dbInstance = this.dbInstance; AllowedGroups.upsertPending(groupId, name, null); } catch {} | 
		
	
		
			
				|  |  |  |  | 					this.cacheActiveGroups(); | 
		
	
		
			
				|  |  |  |  | 					return name; | 
		
	
		
			
				|  |  |  |  | 				} | 
		
	
		
			
				|  |  |  |  | 			} catch {} | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 			// 3) API (evitar en tests)
 | 
		
	
		
			
				|  |  |  |  | 			if (process.env.NODE_ENV !== 'test') { | 
		
	
		
			
				|  |  |  |  | 				const groups = await this.fetchGroupsFromAPI(); | 
		
	
		
			
				|  |  |  |  | 				const g = groups.find((gg) => gg?.id === groupId); | 
		
	
		
			
				|  |  |  |  | 				const subject = g?.subject ? String(g.subject).trim() : ''; | 
		
	
		
			
				|  |  |  |  | 				if (subject) { | 
		
	
		
			
				|  |  |  |  | 					try { this.ensureGroupExists(groupId, subject); } catch {} | 
		
	
		
			
				|  |  |  |  | 					try { (AllowedGroups as any).dbInstance = this.dbInstance; AllowedGroups.upsertPending(groupId, subject, null); } catch {} | 
		
	
		
			
				|  |  |  |  | 					this.cacheActiveGroups(); | 
		
	
		
			
				|  |  |  |  | 					return subject; | 
		
	
		
			
				|  |  |  |  | 				} | 
		
	
		
			
				|  |  |  |  | 			} | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 			return null; | 
		
	
		
			
				|  |  |  |  | 		} catch { | 
		
	
		
			
				|  |  |  |  | 			return null; | 
		
	
		
			
				|  |  |  |  | 		} | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 	/** | 
		
	
		
			
				|  |  |  |  | 	 * Sincroniza miembros para un grupo concreto (útil tras detectar un grupo nuevo). | 
		
	
		
			
				|  |  |  |  | 	 */ | 
		
	
	
		
			
				
					|  |  |  | 
 |