@ -4,6 +4,8 @@ import { toIsoSqlUTC } from '../utils/datetime';
export class MaintenanceService {
export class MaintenanceService {
private static _timer : any = null ;
private static _timer : any = null ;
private static _healthCheckTimer : any = null ;
private static _lastRestartAttempt : number = 0 ;
private static get retentionDays ( ) : number {
private static get retentionDays ( ) : number {
const v = Number ( process . env . GROUP_MEMBERS_INACTIVE_RETENTION_DAYS ) ;
const v = Number ( process . env . GROUP_MEMBERS_INACTIVE_RETENTION_DAYS ) ;
@ -11,19 +13,44 @@ export class MaintenanceService {
return 180 ; // por defecto 180 días
return 180 ; // por defecto 180 días
}
}
private static get evolutionApiConfig() {
return {
url : process.env.EVOLUTION_API_URL ,
instance : process.env.EVOLUTION_API_INSTANCE ,
apiKey : process.env.EVOLUTION_API_KEY ,
intervalMs : Number ( process . env . HEALTH_CHECK_INTERVAL_MS || '120000' ) , // 2 min por defecto
restartCooldownMs : Number ( process . env . HEALTH_CHECK_RESTART_COOLDOWN_MS || '900000' ) , // 15 min por defecto
} ;
}
static start ( ) : void {
static start ( ) : void {
if ( process . env . NODE_ENV === 'test' && process . env . FORCE_SCHEDULERS !== 'true' ) return ;
if ( process . env . NODE_ENV === 'test' && process . env . FORCE_SCHEDULERS !== 'true' ) return ;
if ( this . retentionDays <= 0 ) return ;
// --- Tareas diarias existentes ---
const intervalMs = 24 * 60 * 60 * 1000 ; // diario
if ( this . retentionDays > 0 ) {
this . _timer = setInterval ( ( ) = > {
const intervalMs = 24 * 60 * 60 * 1000 ; // diario
this . cleanupInactiveMembersOnce ( ) . catch ( err = > {
this . _timer = setInterval ( ( ) = > {
console . error ( '❌ Error en cleanup de miembros inactivos:' , err ) ;
this . cleanupInactiveMembersOnce ( ) . catch ( err = > {
} ) ;
console . error ( '❌ Error en cleanup de miembros inactivos:' , err ) ;
this . reconcileAliasUsersOnce ( ) . catch ( err = > {
} ) ;
console . error ( '❌ Error en reconcile de alias de usuarios:' , err ) ;
this . reconcileAliasUsersOnce ( ) . catch ( err = > {
} ) ;
console . error ( '❌ Error en reconcile de alias de usuarios:' , err ) ;
} , intervalMs ) ;
} ) ;
} , intervalMs ) ;
}
// --- Nuevo Health Check de Evolution API ---
const { url , instance , apiKey , intervalMs } = this . evolutionApiConfig ;
if ( url && instance && apiKey ) {
console . log ( '[MaintenanceService] Iniciando health check de Evolution API...' ) ;
this . _healthCheckTimer = setInterval ( ( ) = > {
this . performEvolutionHealthCheck ( ) . catch ( err = > {
console . error ( '❌ Error en el health check de Evolution API:' , err ) ;
} ) ;
} , intervalMs ) ;
} else {
console . warn ( '[MaintenanceService] Variables de entorno para el health check de Evolution API (URL, INSTANCE, API_KEY) no encontradas. Health check desactivado.' ) ;
}
}
}
static stop ( ) : void {
static stop ( ) : void {
@ -31,6 +58,10 @@ export class MaintenanceService {
clearInterval ( this . _timer ) ;
clearInterval ( this . _timer ) ;
this . _timer = null ;
this . _timer = null ;
}
}
if ( this . _healthCheckTimer ) {
clearInterval ( this . _healthCheckTimer ) ;
this . _healthCheckTimer = null ;
}
}
}
static async cleanupInactiveMembersOnce ( instance : Database = db , retentionDays : number = this . retentionDays ) : Promise < number > {
static async cleanupInactiveMembersOnce ( instance : Database = db , retentionDays : number = this . retentionDays ) : Promise < number > {
@ -98,4 +129,51 @@ export class MaintenanceService {
return 0 ;
return 0 ;
}
}
}
}
/ * *
* Verifica el estado de la instancia de Evolution API y la reinicia si es necesario .
* /
private static async performEvolutionHealthCheck ( ) : Promise < void > {
const { url , instance , apiKey , restartCooldownMs } = this . evolutionApiConfig ;
const stateUrl = ` ${ url } /instance/connectionState/ ${ instance } ` ;
const restartUrl = ` ${ url } /instance/restart/ ${ instance } ` ;
const headers = { apikey : apiKey } ;
try {
const response = await fetch ( stateUrl , { method : 'GET' , headers } ) ;
if ( ! response . ok ) {
console . error ( ` [HealthCheck] Error al consultar estado de Evolution API: ${ response . status } ${ response . statusText } ` ) ;
return ;
}
const data = await response . json ( ) ;
const currentState = data ? . instance ? . state ;
console . log ( ` [HealthCheck] Estado de la instancia ' ${ instance } ': ${ currentState } ` ) ;
if ( currentState !== 'open' ) {
const now = Date . now ( ) ;
if ( now - this . _lastRestartAttempt > restartCooldownMs ) {
console . warn ( ` [HealthCheck] La instancia no está 'open'. Estado actual: ${ currentState } . Intentando reiniciar... ` ) ;
try {
const restartResponse = await fetch ( restartUrl , { method : 'PUT' , headers } ) ;
if ( restartResponse . ok ) {
console . log ( ` [HealthCheck] Petición de reinicio para ' ${ instance } ' enviada exitosamente. ` ) ;
this . _lastRestartAttempt = now ;
} else {
console . error ( ` [HealthCheck] Fallo al reiniciar la instancia. Status: ${ restartResponse . status } ${ restartResponse . statusText } ` ) ;
}
} catch ( restartError ) {
console . error ( '[HealthCheck] Error de red al intentar reiniciar la instancia:' , restartError ) ;
}
} else {
console . log ( ` [HealthCheck] La instancia no está 'open', pero esperando cooldown de ${ Math . round ( restartCooldownMs / 60000 ) } minutos para no sobrecargar la API. ` ) ;
}
}
} catch ( error ) {
console . error ( '[HealthCheck] Error de red o inesperado al verificar el estado de la Evolution API:' , error ) ;
}
}
}
}