refactor: eliminar as any y tipar servicios y DB en lote 3

Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>
main
brobert 1 month ago
parent 1ecf8a5ff2
commit 83e11a69ab

@ -29,7 +29,8 @@ export function getDb(filename: string = 'tasks.db'): Database {
try { try {
mkdirSync(dirname(absolutePath), { recursive: true }); mkdirSync(dirname(absolutePath), { recursive: true });
} catch (err) { } catch (err) {
if ((err as any)?.code !== 'EEXIST') throw err; // Solo ignorar "ya existe" const code = (err && typeof err === 'object') ? (err as { code?: string }).code : undefined;
if (code !== 'EEXIST') throw err; // Solo ignorar "ya existe"
} }
const instance = new Database(absolutePath); const instance = new Database(absolutePath);
applyDefaultPragmas(instance); applyDefaultPragmas(instance);
@ -45,7 +46,8 @@ export function getDb(filename: string = 'tasks.db'): Database {
try { try {
mkdirSync(dirPath, { recursive: true }); mkdirSync(dirPath, { recursive: true });
} catch (err) { } catch (err) {
if ((err as any)?.code !== 'EEXIST') throw err; // Only ignore "already exists" errors const code = (err && typeof err === 'object') ? (err as { code?: string }).code : undefined;
if (code !== 'EEXIST') throw err; // Only ignore "already exists" errors
} }
const instance = new Database(join(dirPath, filename)); const instance = new Database(join(dirPath, filename));
@ -75,7 +77,7 @@ export function initializeDatabase(instance: Database) {
try { try {
const row = instance const row = instance
.query(`SELECT name FROM sqlite_master WHERE type='table' AND name = ?`) .query(`SELECT name FROM sqlite_master WHERE type='table' AND name = ?`)
.get(name) as any; .get(name) as { name?: string } | undefined;
return Boolean(row && row.name === name); return Boolean(row && row.name === name);
} catch { } catch {
return false; return false;

@ -8,7 +8,7 @@ export type Migration = {
}; };
function tableHasColumn(db: Database, table: string, column: string): boolean { function tableHasColumn(db: Database, table: string, column: string): boolean {
const cols = db.query(`PRAGMA table_info(${table})`).all() as any[]; const cols = db.query(`PRAGMA table_info(${table})`).all() as Array<{ name?: string }>;
return Array.isArray(cols) && cols.some((c: any) => c.name === column); return Array.isArray(cols) && cols.some((c: any) => c.name === column);
} }
@ -250,7 +250,7 @@ export const migrations: Migration[] = [
up: (db: Database) => { up: (db: Database) => {
// Añadir columna display_code si no existe // Añadir columna display_code si no existe
try { try {
const cols = db.query(`PRAGMA table_info(tasks)`).all() as any[]; const cols = db.query(`PRAGMA table_info(tasks)`).all() as Array<{ name?: string }>;
const hasDisplay = Array.isArray(cols) && cols.some((c: any) => String(c.name) === 'display_code'); const hasDisplay = Array.isArray(cols) && cols.some((c: any) => String(c.name) === 'display_code');
if (!hasDisplay) { if (!hasDisplay) {
db.exec(`ALTER TABLE tasks ADD COLUMN display_code INTEGER NULL;`); db.exec(`ALTER TABLE tasks ADD COLUMN display_code INTEGER NULL;`);
@ -368,7 +368,7 @@ export const migrations: Migration[] = [
// Añadir columna para poder mostrar siempre la URL (guardando el token en claro). // Añadir columna para poder mostrar siempre la URL (guardando el token en claro).
// Nota: mantenemos token_hash para validación; token_plain se usa solo para construir la URL en UI. // Nota: mantenemos token_hash para validación; token_plain se usa solo para construir la URL en UI.
try { try {
const cols = db.query(`PRAGMA table_info(calendar_tokens)`).all() as any[]; const cols = db.query(`PRAGMA table_info(calendar_tokens)`).all() as Array<{ name?: string }>;
const hasPlain = Array.isArray(cols) && cols.some((c: any) => String(c.name) === 'token_plain'); const hasPlain = Array.isArray(cols) && cols.some((c: any) => String(c.name) === 'token_plain');
if (!hasPlain) { if (!hasPlain) {
db.exec(`ALTER TABLE calendar_tokens ADD COLUMN token_plain TEXT NULL;`); db.exec(`ALTER TABLE calendar_tokens ADD COLUMN token_plain TEXT NULL;`);
@ -382,7 +382,7 @@ export const migrations: Migration[] = [
checksum: 'v13-groups-onboarding-2025-10-17', checksum: 'v13-groups-onboarding-2025-10-17',
up: (db: Database) => { up: (db: Database) => {
try { try {
const cols = db.query(`PRAGMA table_info(groups)`).all() as any[]; const cols = db.query(`PRAGMA table_info(groups)`).all() as Array<{ name?: string }>;
const hasCol = Array.isArray(cols) && cols.some((c: any) => String(c.name) === 'onboarding_prompted_at'); const hasCol = Array.isArray(cols) && cols.some((c: any) => String(c.name) === 'onboarding_prompted_at');
if (!hasCol) { if (!hasCol) {
db.exec(`ALTER TABLE groups ADD COLUMN onboarding_prompted_at TEXT NULL;`); db.exec(`ALTER TABLE groups ADD COLUMN onboarding_prompted_at TEXT NULL;`);
@ -396,7 +396,7 @@ export const migrations: Migration[] = [
checksum: 'v14-groups-archived-2025-10-19', checksum: 'v14-groups-archived-2025-10-19',
up: (db: Database) => { up: (db: Database) => {
try { try {
const cols = db.query(`PRAGMA table_info(groups)`).all() as any[]; const cols = db.query(`PRAGMA table_info(groups)`).all() as Array<{ name?: string }>;
const hasArchived = Array.isArray(cols) && cols.some((c: any) => String(c.name) === 'archived'); const hasArchived = Array.isArray(cols) && cols.some((c: any) => String(c.name) === 'archived');
if (!hasArchived) { if (!hasArchived) {
db.exec(`ALTER TABLE groups ADD COLUMN archived BOOLEAN NOT NULL DEFAULT 0;`); db.exec(`ALTER TABLE groups ADD COLUMN archived BOOLEAN NOT NULL DEFAULT 0;`);
@ -443,7 +443,7 @@ export const migrations: Migration[] = [
checksum: 'v16-groups-is-community-2025-10-19', checksum: 'v16-groups-is-community-2025-10-19',
up: (db: Database) => { up: (db: Database) => {
try { try {
const cols = db.query(`PRAGMA table_info(groups)`).all() as any[]; const cols = db.query(`PRAGMA table_info(groups)`).all() as Array<{ name?: string }>;
const hasCol = Array.isArray(cols) && cols.some((c: any) => String(c.name) === 'is_community'); const hasCol = Array.isArray(cols) && cols.some((c: any) => String(c.name) === 'is_community');
if (!hasCol) { if (!hasCol) {
db.exec(`ALTER TABLE groups ADD COLUMN is_community BOOLEAN NOT NULL DEFAULT 0;`); db.exec(`ALTER TABLE groups ADD COLUMN is_community BOOLEAN NOT NULL DEFAULT 0;`);
@ -479,7 +479,7 @@ export const migrations: Migration[] = [
checksum: 'v18-task-origins-participant-fromme-2025-10-21', checksum: 'v18-task-origins-participant-fromme-2025-10-21',
up: (db: Database) => { up: (db: Database) => {
try { try {
const cols = db.query(`PRAGMA table_info(task_origins)`).all() as any[]; const cols = db.query(`PRAGMA table_info(task_origins)`).all() as Array<{ name?: string }>;
const hasParticipant = Array.isArray(cols) && cols.some((c: any) => String(c.name) === 'participant'); const hasParticipant = Array.isArray(cols) && cols.some((c: any) => String(c.name) === 'participant');
if (!hasParticipant) { if (!hasParticipant) {
db.exec(`ALTER TABLE task_origins ADD COLUMN participant TEXT NULL;`); db.exec(`ALTER TABLE task_origins ADD COLUMN participant TEXT NULL;`);
@ -497,7 +497,7 @@ export const migrations: Migration[] = [
checksum: 'v19-users-last-command-at-2025-10-25', checksum: 'v19-users-last-command-at-2025-10-25',
up: (db: Database) => { up: (db: Database) => {
try { try {
const cols = db.query(`PRAGMA table_info(users)`).all() as any[]; const cols = db.query(`PRAGMA table_info(users)`).all() as Array<{ name?: string }>;
const hasCol = Array.isArray(cols) && cols.some((c: any) => String(c.name) === 'last_command_at'); const hasCol = Array.isArray(cols) && cols.some((c: any) => String(c.name) === 'last_command_at');
if (!hasCol) { if (!hasCol) {
db.exec(`ALTER TABLE users ADD COLUMN last_command_at TEXT NULL;`); db.exec(`ALTER TABLE users ADD COLUMN last_command_at TEXT NULL;`);

@ -35,7 +35,7 @@ function ensureMigrationsTable(db: Database) {
} }
function getAppliedVersions(db: Database): Map<number, { name: string; checksum: string; applied_at: string }> { function getAppliedVersions(db: Database): Map<number, { name: string; checksum: string; applied_at: string }> {
const rows = db.query(`SELECT version, name, checksum, applied_at FROM schema_migrations ORDER BY version`).all() as any[]; const rows = db.query(`SELECT version, name, checksum, applied_at FROM schema_migrations ORDER BY version`).all() as Array<{ version: number; name: string; checksum: string; applied_at: string }>;
const map = new Map<number, { name: string; checksum: string; applied_at: string }>(); const map = new Map<number, { name: string; checksum: string; applied_at: string }>();
for (const r of rows) { for (const r of rows) {
map.set(Number(r.version), { name: String(r.name), checksum: String(r.checksum), applied_at: String(r.applied_at) }); map.set(Number(r.version), { name: String(r.name), checksum: String(r.checksum), applied_at: String(r.applied_at) });
@ -44,7 +44,7 @@ function getAppliedVersions(db: Database): Map<number, { name: string; checksum:
} }
function tableExists(db: Database, table: string): boolean { function tableExists(db: Database, table: string): boolean {
const row = db.query(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`).get(table) as any; const row = db.query(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`).get(table) as { name?: string } | undefined;
return !!row; return !!row;
} }
@ -103,8 +103,8 @@ export const Migrator = {
} }
// Resumen inicial // Resumen inicial
const jmRow = db.query(`PRAGMA journal_mode`).get() as any; const jmRow = db.query(`PRAGMA journal_mode`).get() as Record<string, unknown> | undefined;
const journalMode = jmRow ? (jmRow.journal_mode || jmRow.value || jmRow.mode || 'unknown') : 'unknown'; const journalMode = jmRow ? String((jmRow['journal_mode'] ?? jmRow['value'] ?? jmRow['mode'] ?? 'unknown')) : 'unknown';
const currentVersion = applied.size ? Math.max(...Array.from(applied.keys())) : 0; const currentVersion = applied.size ? Math.max(...Array.from(applied.keys())) : 0;
if (!MIGRATIONS_QUIET) console.log(` Migrador — journal_mode=${journalMode}, versión_actual=${currentVersion}, pendientes=${pending.length}`); if (!MIGRATIONS_QUIET) console.log(` Migrador — journal_mode=${journalMode}, versión_actual=${currentVersion}, pendientes=${pending.length}`);
try { logEvent('info', 'startup_summary', { journal_mode: journalMode, current_version: currentVersion, pending: pending.length }); } catch {} try { logEvent('info', 'startup_summary', { journal_mode: journalMode, current_version: currentVersion, pending: pending.length }); } catch {}

@ -354,16 +354,16 @@ export class WebhookServer {
// Etapa 2: Descubrimiento seguro de grupos (modo 'discover') // Etapa 2: Descubrimiento seguro de grupos (modo 'discover')
if (isGroupId(remoteJid)) { if (isGroupId(remoteJid)) {
try { (AllowedGroups as any).dbInstance = WebhookServer.dbInstance; } catch {} try { AllowedGroups.dbInstance = WebhookServer.dbInstance; } catch {}
const gatingMode = String(process.env.GROUP_GATING_MODE || 'off').toLowerCase(); const gatingMode = String(process.env.GROUP_GATING_MODE || 'off').toLowerCase();
if (gatingMode === 'discover') { if (gatingMode === 'discover') {
try { try {
const exists = WebhookServer.dbInstance const exists = WebhookServer.dbInstance
.prepare(`SELECT 1 FROM allowed_groups WHERE group_id = ? LIMIT 1`) .prepare(`SELECT 1 FROM allowed_groups WHERE group_id = ? LIMIT 1`)
.get(remoteJid) as any; .get(remoteJid);
if (!exists) { if (!exists) {
try { await GroupSyncService.ensureGroupLabelAndName(remoteJid); } catch {} try { await GroupSyncService.ensureGroupLabelAndName(remoteJid); } catch {}
try { (AllowedGroups as any).dbInstance = WebhookServer.dbInstance; } catch {} try { AllowedGroups.dbInstance = WebhookServer.dbInstance; } catch {}
try { AllowedGroups.upsertPending(remoteJid, (GroupSyncService.activeGroupsCache.get(remoteJid) || null), normalizedSenderId); } catch {} try { AllowedGroups.upsertPending(remoteJid, (GroupSyncService.activeGroupsCache.get(remoteJid) || null), normalizedSenderId); } catch {}
try { Metrics.inc('unknown_groups_discovered_total'); } catch {} try { Metrics.inc('unknown_groups_discovered_total'); } catch {}
try { try {
@ -382,7 +382,7 @@ export class WebhookServer {
} catch { } catch {
// Si la tabla no existe por alguna razón, intentar upsert y retornar igualmente // Si la tabla no existe por alguna razón, intentar upsert y retornar igualmente
try { await GroupSyncService.ensureGroupLabelAndName(remoteJid); } catch {} try { await GroupSyncService.ensureGroupLabelAndName(remoteJid); } catch {}
try { (AllowedGroups as any).dbInstance = WebhookServer.dbInstance; } catch {} try { AllowedGroups.dbInstance = WebhookServer.dbInstance; } catch {}
try { AllowedGroups.upsertPending(remoteJid, (GroupSyncService.activeGroupsCache.get(remoteJid) || null), normalizedSenderId); } catch {} try { AllowedGroups.upsertPending(remoteJid, (GroupSyncService.activeGroupsCache.get(remoteJid) || null), normalizedSenderId); } catch {}
try { Metrics.inc('unknown_groups_discovered_total'); } catch {} try { Metrics.inc('unknown_groups_discovered_total'); } catch {}
try { try {
@ -403,7 +403,7 @@ export class WebhookServer {
// Etapa 3: Gating en modo 'enforce' — ignorar mensajes de grupos no permitidos // Etapa 3: Gating en modo 'enforce' — ignorar mensajes de grupos no permitidos
if (isGroupId(remoteJid)) { if (isGroupId(remoteJid)) {
try { (AllowedGroups as any).dbInstance = WebhookServer.dbInstance; } catch {} try { AllowedGroups.dbInstance = WebhookServer.dbInstance; } catch {}
const gatingMode2 = String(process.env.GROUP_GATING_MODE || 'off').toLowerCase(); const gatingMode2 = String(process.env.GROUP_GATING_MODE || 'off').toLowerCase();
if (gatingMode2 === 'enforce') { if (gatingMode2 === 'enforce') {
try { try {
@ -420,8 +420,8 @@ export class WebhookServer {
// Manejo de comandos de administración (/admin) antes de cualquier otra lógica de grupo // Manejo de comandos de administración (/admin) antes de cualquier otra lógica de grupo
if (messageTextTrimmed.startsWith('/admin')) { if (messageTextTrimmed.startsWith('/admin')) {
try { (AdminService as any).dbInstance = WebhookServer.dbInstance; } catch {} try { AdminService.dbInstance = WebhookServer.dbInstance; } catch {}
try { (AllowedGroups as any).dbInstance = WebhookServer.dbInstance; } catch {} try { AllowedGroups.dbInstance = WebhookServer.dbInstance; } catch {}
const adminResponses = await AdminService.handle({ const adminResponses = await AdminService.handle({
sender: normalizedSenderId, sender: normalizedSenderId,
groupId: remoteJid, groupId: remoteJid,
@ -477,8 +477,8 @@ export class WebhookServer {
|| []; || [];
// Asegurar que CommandService y TaskService usen la misma DB (tests/producción) // Asegurar que CommandService y TaskService usen la misma DB (tests/producción)
(CommandService as any).dbInstance = WebhookServer.dbInstance; CommandService.dbInstance = WebhookServer.dbInstance;
(TaskService as any).dbInstance = WebhookServer.dbInstance; TaskService.dbInstance = WebhookServer.dbInstance;
// Delegar el manejo del comando // Delegar el manejo del comando
const messageId = typeof data?.key?.id === 'string' ? data.key.id : null; const messageId = typeof data?.key?.id === 'string' ? data.key.id : null;
@ -514,7 +514,7 @@ export class WebhookServer {
if (scope !== 'all' && !isGroup) return; if (scope !== 'all' && !isGroup) return;
// Respetar gating 'enforce' // Respetar gating 'enforce'
try { (AllowedGroups as any).dbInstance = WebhookServer.dbInstance; } catch {} try { AllowedGroups.dbInstance = WebhookServer.dbInstance; } catch {}
const mode = String(process.env.GROUP_GATING_MODE || 'off').toLowerCase(); const mode = String(process.env.GROUP_GATING_MODE || 'off').toLowerCase();
if (mode === 'enforce' && isGroup) { if (mode === 'enforce' && isGroup) {
try { try {

@ -61,7 +61,7 @@ export class AdminService {
} }
// Asegurar acceso a la misma DB para AllowedGroups // Asegurar acceso a la misma DB para AllowedGroups
try { (AllowedGroups as any).dbInstance = this.dbInstance; } catch {} try { AllowedGroups.dbInstance = this.dbInstance; } catch {}
const raw = String(ctx.message || '').trim(); const raw = String(ctx.message || '').trim();
const lower = raw.toLowerCase(); const lower = raw.toLowerCase();
@ -251,7 +251,7 @@ export class AdminService {
rest.startsWith('list-all ') rest.startsWith('list-all ')
) { ) {
// Asegurar acceso a la misma DB para TaskService // Asegurar acceso a la misma DB para TaskService
try { (TaskService as any).dbInstance = this.dbInstance; } catch {} try { TaskService.dbInstance = this.dbInstance; } catch {}
const DEFAULT_LIMIT = 50; const DEFAULT_LIMIT = 50;
let limit = DEFAULT_LIMIT; let limit = DEFAULT_LIMIT;

@ -49,7 +49,7 @@ export class CommandService {
try { try {
let usersTableExists = false; let usersTableExists = false;
try { try {
const row = this.dbInstance.query(`SELECT name FROM sqlite_master WHERE type='table' AND name='users'`).get() as any; const row = this.dbInstance.query(`SELECT name FROM sqlite_master WHERE type='table' AND name='users'`).get() as { name?: string } | undefined;
usersTableExists = !!row; usersTableExists = !!row;
} catch {} } catch {}
if (usersTableExists) { if (usersTableExists) {
@ -64,7 +64,7 @@ export class CommandService {
// Gating de grupos en modo 'enforce' (cuando CommandService se invoca directamente) // Gating de grupos en modo 'enforce' (cuando CommandService se invoca directamente)
if (isGroupId(context.groupId)) { if (isGroupId(context.groupId)) {
try { (AllowedGroups as any).dbInstance = this.dbInstance; } catch { } try { AllowedGroups.dbInstance = this.dbInstance; } catch { }
const mode = String(process.env.GROUP_GATING_MODE || 'off').toLowerCase(); const mode = String(process.env.GROUP_GATING_MODE || 'off').toLowerCase();
if (mode === 'enforce') { if (mode === 'enforce') {
try { try {

@ -12,6 +12,13 @@ import { handleCompletar } from './handlers/completar';
import { handleTomar } from './handlers/tomar'; import { handleTomar } from './handlers/tomar';
import { handleSoltar } from './handlers/soltar'; import { handleSoltar } from './handlers/soltar';
import { handleNueva } from './handlers/nueva'; import { handleNueva } from './handlers/nueva';
type NuevaCtx = Parameters<typeof handleNueva>[0];
type VerCtx = Parameters<typeof handleVer>[0];
type CompletarCtx = Parameters<typeof handleCompletar>[0];
type TomarCtx = Parameters<typeof handleTomar>[0];
type SoltarCtx = Parameters<typeof handleSoltar>[0];
type ConfigurarCtx = Parameters<typeof handleConfigurar>[0];
type WebCtx = Parameters<typeof handleWeb>[0];
import { ResponseQueue } from '../response-queue'; import { ResponseQueue } from '../response-queue';
import { isGroupId } from '../../utils/whatsapp'; import { isGroupId } from '../../utils/whatsapp';
import { Metrics } from '../metrics'; import { Metrics } from '../metrics';
@ -105,7 +112,7 @@ export async function route(context: RouteContext, deps?: { db: Database }): Pro
if (action === 'nueva') { if (action === 'nueva') {
try { ResponseQueue.setOnboardingAggregatesMetrics(); } catch {} try { ResponseQueue.setOnboardingAggregatesMetrics(); } catch {}
return await handleNueva(context as any, { db: database }); return await handleNueva(context as unknown as NuevaCtx, { db: database });
} }
if (action === 'ver') { if (action === 'ver') {
@ -129,32 +136,32 @@ export async function route(context: RouteContext, deps?: { db: Database }): Pro
}]; }];
} }
return await handleVer(context as any); return await handleVer(context as unknown as VerCtx);
} }
if (action === 'completar') { if (action === 'completar') {
try { ResponseQueue.setOnboardingAggregatesMetrics(); } catch {} try { ResponseQueue.setOnboardingAggregatesMetrics(); } catch {}
return await handleCompletar(context as any); return await handleCompletar(context as unknown as CompletarCtx);
} }
if (action === 'tomar') { if (action === 'tomar') {
try { ResponseQueue.setOnboardingAggregatesMetrics(); } catch {} try { ResponseQueue.setOnboardingAggregatesMetrics(); } catch {}
return await handleTomar(context as any); return await handleTomar(context as unknown as TomarCtx);
} }
if (action === 'soltar') { if (action === 'soltar') {
try { ResponseQueue.setOnboardingAggregatesMetrics(); } catch {} try { ResponseQueue.setOnboardingAggregatesMetrics(); } catch {}
return await handleSoltar(context as any); return await handleSoltar(context as unknown as SoltarCtx);
} }
if (action === 'configurar') { if (action === 'configurar') {
try { ResponseQueue.setOnboardingAggregatesMetrics(); } catch {} try { ResponseQueue.setOnboardingAggregatesMetrics(); } catch {}
return handleConfigurar(context as any, { db: database }); return handleConfigurar(context as unknown as ConfigurarCtx, { db: database });
} }
if (action === 'web') { if (action === 'web') {
try { ResponseQueue.setOnboardingAggregatesMetrics(); } catch {} try { ResponseQueue.setOnboardingAggregatesMetrics(); } catch {}
return await handleWeb(context as any, { db: database }); return await handleWeb(context as unknown as WebCtx, { db: database });
} }
// Desconocido → ayuda rápida // Desconocido → ayuda rápida

@ -104,7 +104,7 @@ export class ContactsService {
return null; return null;
} }
const data = await res.json().catch(() => null as any); const data = await res.json().catch(() => null);
const arrayCandidates: any[] = Array.isArray(data) const arrayCandidates: any[] = Array.isArray(data)
? data ? data
: Array.isArray((data as any)?.contacts) : Array.isArray((data as any)?.contacts)

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

@ -226,7 +226,7 @@ export const ResponseQueue = {
AND status IN ('queued','processing','sent') AND status IN ('queued','processing','sent')
AND (updated_at > ? OR created_at > ?) AND (updated_at > ? OR created_at > ?)
LIMIT 1 LIMIT 1
`).get(metadata, cutoff, cutoff) as any; `).get(metadata, cutoff, cutoff);
if (exists) { if (exists) {
return; return;

@ -96,7 +96,7 @@ export class TaskService {
ensuredCreator, ensuredCreator,
displayCode displayCode
); );
const taskId = Number((runResult as any).lastInsertRowid); const taskId = Number((runResult as { lastInsertRowid?: number | bigint }).lastInsertRowid);
if (assignments.length > 0) { if (assignments.length > 0) {
const insertAssignment = this.dbInstance.prepare(` const insertAssignment = this.dbInstance.prepare(`
@ -252,7 +252,7 @@ export class TaskService {
FROM tasks FROM tasks
WHERE id = ? WHERE id = ?
`) `)
.get(taskId) as any; .get(taskId) as { id?: number; description?: string; due_date?: string | null; completed?: number; completed_at?: string | null; display_code?: number | null; group_id?: string | null } | undefined;
if (!existing) { if (!existing) {
return { status: 'not_found' }; return { status: 'not_found' };
@ -291,13 +291,13 @@ export class TaskService {
SELECT chat_id, message_id, created_at, participant, from_me SELECT chat_id, message_id, created_at, participant, from_me
FROM task_origins FROM task_origins
WHERE task_id = ? WHERE task_id = ?
`).get(taskId) as any; `).get(taskId) as { chat_id?: string; message_id?: string; created_at?: string } | undefined;
} catch { } catch {
origin = this.dbInstance.prepare(` origin = this.dbInstance.prepare(`
SELECT chat_id, message_id, created_at SELECT chat_id, message_id, created_at
FROM task_origins FROM task_origins
WHERE task_id = ? WHERE task_id = ?
`).get(taskId) as any; `).get(taskId) as { id?: number; description?: string; due_date?: string | null; group_id?: string | null; completed?: number; completed_at?: string | null; display_code?: number | null } | undefined;
} }
if (origin && origin.chat_id && origin.message_id) { if (origin && origin.chat_id && origin.message_id) {
@ -515,7 +515,7 @@ export class TaskService {
SUM(CASE WHEN user_id = ? THEN 1 ELSE 0 END) AS mine SUM(CASE WHEN user_id = ? THEN 1 ELSE 0 END) AS mine
FROM task_assignments FROM task_assignments
WHERE task_id = ? WHERE task_id = ?
`).get(ensuredUser, taskId) as any; `).get(ensuredUser, taskId) as { cnt?: number; mine?: number } | undefined;
const cnt = Number(stats?.cnt || 0); const cnt = Number(stats?.cnt || 0);
const mine = Number(stats?.mine || 0) > 0; const mine = Number(stats?.mine || 0) > 0;
if (existing.group_id == null && cnt === 1 && mine) { if (existing.group_id == null && cnt === 1 && mine) {
@ -537,7 +537,7 @@ export class TaskService {
WHERE task_id = ? AND user_id = ? WHERE task_id = ? AND user_id = ?
`); `);
const result = deleteStmt.run(taskId, ensuredUser) as any; const result = deleteStmt.run(taskId, ensuredUser) as { changes?: number };
const cntRow = this.dbInstance const cntRow = this.dbInstance
.prepare(`SELECT COUNT(*) as cnt FROM task_assignments WHERE task_id = ?`) .prepare(`SELECT COUNT(*) as cnt FROM task_assignments WHERE task_id = ?`)
@ -592,7 +592,7 @@ export class TaskService {
completed_at completed_at
FROM tasks FROM tasks
WHERE id = ? WHERE id = ?
`).get(taskId) as any; `).get(taskId) as { id?: number; description?: string; due_date?: string | null; group_id?: string | null; completed?: number; completed_at?: string | null; display_code?: number | null } | undefined;
if (!row) return null; if (!row) return null;
return { return {
id: Number(row.id), id: Number(row.id),

Loading…
Cancel
Save