@ -91,30 +91,30 @@ export class GroupSyncService {
console . log ( 'ℹ ️ Grupos crudos de la API:' , JSON . stringify ( groups , null , 2 ) ) ;
console . log ( 'ℹ ️ Sin filtrar por comunidad (modo multicomunidad). Total grupos:' , groups . length ) ;
const dbGroupsBefore = this . dbInstance . prepare ( 'SELECT id, active, COALESCE(archived,0) AS archived, COALESCE(is_community,0) AS is_community, name FROM groups' ) . all ( ) ;
const dbGroupsBefore = this . dbInstance . prepare ( 'SELECT id, active, COALESCE(archived,0) AS archived, COALESCE(is_community,0) AS is_community, name FROM groups' ) . all ( ) as Array < { id : string ; active : number ; archived : number ; is_community : number ; name? : string | null } > ;
console . log ( 'ℹ ️ Grupos en DB antes de upsert:' , dbGroupsBefore ) ;
const result = await this . upsertGroups ( groups ) ;
const dbGroupsAfter = this . dbInstance . prepare ( 'SELECT id, active, COALESCE(archived,0) AS archived, COALESCE(is_community,0) AS is_community, name FROM groups' ) . all ( ) ;
const dbGroupsAfter = this . dbInstance . prepare ( 'SELECT id, active, COALESCE(archived,0) AS archived, COALESCE(is_community,0) AS is_community, name FROM groups' ) . all ( ) as Array < { id : string ; active : number ; archived : number ; is_community : number ; name? : string | null } > ;
console . log ( 'ℹ ️ Grupos en DB después de upsert:' , dbGroupsAfter ) ;
// Detectar grupos que pasaron de activos a inactivos (y no están archivados) en este sync
try {
const beforeMap = new Map < string , { active : number ; archived : number ; is_community : number ; name ? : string | null } > ( ) ;
for ( const r of dbGroupsBefore as any [ ] ) {
beforeMap . set ( String ( r . id ) , { active : Number ( r . active || 0 ) , archived : Number ( r . archived || 0 ) , is_community : Number ( ( r as any ) . is_community || 0 ) , name : r.name ? String ( r . name ) : null } ) ;
for ( const r of dbGroupsBefore ) {
beforeMap . set ( String ( r . id ) , { active : Number ( r . active || 0 ) , archived : Number ( r . archived || 0 ) , is_community : Number ( r . is_community || 0 ) , name : r.name ? String ( r . name ) : null } ) ;
}
const afterMap = new Map < string , { active : number ; archived : number ; is_community : number ; name ? : string | null } > ( ) ;
for ( const r of dbGroupsAfter as any [ ] ) {
afterMap . set ( String ( r . id ) , { active : Number ( r . active || 0 ) , archived : Number ( r . archived || 0 ) , is_community : Number ( ( r as any ) . is_community || 0 ) , name : r.name ? String ( r . name ) : null } ) ;
for ( const r of dbGroupsAfter ) {
afterMap . set ( String ( r . id ) , { active : Number ( r . active || 0 ) , archived : Number ( r . archived || 0 ) , is_community : Number ( r . is_community || 0 ) , name : r.name ? String ( r . name ) : null } ) ;
}
// Determinar grupos que pasaron a estar activos (nuevos o reactivados)
const newlyActivatedLocal : string [ ] = [ ] ;
for ( const [ id , a ] of afterMap . entries ( ) ) {
const b = beforeMap . get ( id ) ;
const becameActive = Number ( a . active ) === 1 && Number ( a . archived ) === 0 && Number ( ( a as any ) . is_community || 0 ) === 0 ;
const becameActive = Number ( a . active ) === 1 && Number ( a . archived ) === 0 && Number ( a . is_community || 0 ) === 0 ;
if ( becameActive && ( ! b || Number ( b . active ) !== 1 ) ) {
newlyActivatedLocal . push ( id ) ;
}
@ -179,12 +179,12 @@ export class GroupSyncService {
}
// Completar labels faltantes en allowed_groups usando todos los grupos devueltos por la API
try { ( AllowedGroups as any ) . dbInstance = this . dbInstance ; this . fillMissingAllowedGroupLabels ( groups ) ; } catch { }
try { AllowedGroups . dbInstance = this . dbInstance ; this . fillMissingAllowedGroupLabels ( groups ) ; } catch { }
// Actualizar métricas
this . cacheActiveGroups ( ) ;
Metrics . set ( 'active_groups' , this . activeGroupsCache . size ) ;
const rowM = this . dbInstance . prepare ( ` SELECT COUNT(*) AS c FROM group_members WHERE is_active = 1 ` ) . get ( ) as any ;
const rowM = this . dbInstance . prepare ( ` SELECT COUNT(*) AS c FROM group_members WHERE is_active = 1 ` ) . get ( ) as { c? : number } | undefined ;
Metrics . set ( 'active_members' , Number ( rowM ? . c || 0 ) ) ;
Metrics . set ( 'last_sync_timestamp_seconds' , Math . floor ( Date . now ( ) / 1000 ) ) ;
Metrics . set ( 'last_sync_ok' , 1 ) ;
@ -321,7 +321,7 @@ export class GroupSyncService {
SELECT group_id AS id
FROM allowed_groups
WHERE label IS NULL OR TRIM ( label ) = ''
` ).all() as any[] ;
` ).all() as Array<{ id: string }> ;
if ( ! rows || rows . length === 0 ) return 0 ;
let filled = 0 ;
@ -453,7 +453,7 @@ export class GroupSyncService {
// - Si es grupo "comunidad/announce", bloquearlo.
// - En caso contrario, upsert pending y label.
try {
( AllowedGroups as any ) . dbInstance = this . dbInstance ;
AllowedGroups . dbInstance = this . dbInstance ;
if ( isCommunityFlag ) {
AllowedGroups . setStatus ( group . id , 'blocked' , group . subject ) ;
} else {
@ -844,7 +844,7 @@ export class GroupSyncService {
try {
const mode = String ( process . env . GROUP_GATING_MODE || 'off' ) . toLowerCase ( ) ;
if ( mode === 'enforce' ) {
try { ( AllowedGroups as any ) . dbInstance = this . dbInstance ; } catch { }
try { AllowedGroups . dbInstance = this . dbInstance ; } catch { }
if ( ! AllowedGroups . isAllowed ( groupId ) ) {
try { Metrics . inc ( 'onboarding_prompts_skipped_total' , 1 , { group_id : groupId , reason : 'not_allowed' } ) ; } catch { }
return ;
@ -941,7 +941,7 @@ export class GroupSyncService {
const mode = String ( process . env . GROUP_GATING_MODE || 'off' ) . toLowerCase ( ) ;
const enforce = mode === 'enforce' ;
if ( enforce ) {
try { ( AllowedGroups as any ) . dbInstance = this . dbInstance ; } catch { }
try { AllowedGroups . dbInstance = this . dbInstance ; } catch { }
}
let groups = 0 , added = 0 , updated = 0 , deactivated = 0 ;
@ -984,7 +984,7 @@ export class GroupSyncService {
const mode = String ( process . env . GROUP_GATING_MODE || 'off' ) . toLowerCase ( ) ;
const enforce = mode === 'enforce' ;
if ( enforce ) {
try { ( AllowedGroups as any ) . dbInstance = this . dbInstance ; } catch { }
try { AllowedGroups . dbInstance = this . dbInstance ; } catch { }
}
let groups = 0 , added = 0 , updated = 0 , deactivated = 0 ;
@ -1106,7 +1106,7 @@ export class GroupSyncService {
* /
public static isSnapshotFresh ( groupId : string , nowMs : number = Date . now ( ) ) : boolean {
try {
const row = this . dbInstance . prepare ( ` SELECT last_verified FROM groups WHERE id = ? ` ) . get ( groupId ) as any ;
const row = this . dbInstance . prepare ( ` SELECT last_verified FROM groups WHERE id = ? ` ) . get ( groupId ) as { last_verified? : string | null } | undefined ;
const lv = row ? . last_verified ? String ( row . last_verified ) : null ;
if ( ! lv ) return false ;
// Persistimos 'YYYY-MM-DD HH:MM:SS[.mmm]'. Convertimos a ISO-like para Date.parse
@ -1146,7 +1146,7 @@ export class GroupSyncService {
WHERE gm . user_id = ? AND gm . is_active = 1 AND g . active = 1
AND COALESCE ( g . is_community , 0 ) = 0
AND COALESCE ( g . archived , 0 ) = 0
` ).all(userId) as any[] ;
` ).all(userId) as Array<{ id: string }> ;
const set = new Set < string > ( ) ;
for ( const r of rows ) {
if ( r ? . id ) set . add ( String ( r . id ) ) ;
@ -1216,18 +1216,18 @@ export class GroupSyncService {
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 { }
try { AllowedGroups . 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 row = this . dbInstance . prepare ( 'SELECT name FROM groups WHERE id = ?' ) . get ( groupId ) as { name? : string | null } | undefined ;
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 { }
try { AllowedGroups . dbInstance = this . dbInstance ; AllowedGroups . upsertPending ( groupId , name , null ) ; } catch { }
this . cacheActiveGroups ( ) ;
return name ;
}
@ -1240,7 +1240,7 @@ export class GroupSyncService {
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 { }
try { AllowedGroups . dbInstance = this . dbInstance ; AllowedGroups . upsertPending ( groupId , subject , null ) ; } catch { }
this . cacheActiveGroups ( ) ;
return subject ;
}
@ -1275,7 +1275,7 @@ export class GroupSyncService {
try {
// Asegurar existencia del grupo en DB (FKs) antes de reconciliar
this . ensureGroupExists ( groupId ) ;
const snapshot = await ( this as any ) . fetchGroupMembersFromAPI ( groupId ) ;
const snapshot = await this . fetchGroupMembersFromAPI ( groupId ) ;
return this . reconcileGroupMembers ( groupId , snapshot ) ;
} catch ( e ) {
console . error ( ` ❌ Failed to sync members for group ${ groupId } : ` , e instanceof Error ? e.message : String ( e ) ) ;