feat(config): modulo Configuraciones admin + permisos por rol
- Tablas ConfiguracionSistema (7 configs) + RolConfiguracion (6 roles) - Endpoints: config/sistema GET/PATCH, config/roles GET/PATCH, config/usuarios GET/PATCH, config/reset-roles POST, config/acciones GET - modulosDeRol() async con fallback a MATRIZ estatica - auth/me devuelve acciones[] segun rol del usuario - saludMonday.estaDisponible() lee modo_local_forzado de DB (cache 30s) - Landing: vista Configuraciones con 4 tabs (Modo, Roles, Usuarios, Sistema) - Mi-dia: boton Descartar se deshabilita si !descartar_lead - 630 API tests + 24 landing tests + 20 mi-dia tests verdes Seed: npm run seed:config
This commit is contained in:
@@ -55,6 +55,7 @@ describe('login', () => {
|
||||
asignacion: 'MONTEBELLO',
|
||||
mondayUserId: null,
|
||||
modulos: ['mi-dia', 'alta-cliente', 'alta-afluencia', 'cotizador', 'mi-tablero', 'contactos', 'chat'],
|
||||
acciones: [],
|
||||
})
|
||||
expect(verificarToken(resultado.token).username).toBe('erick israel pena ramos')
|
||||
})
|
||||
|
||||
@@ -5,6 +5,7 @@ import { prisma } from './prisma.js'
|
||||
import { config } from '../config/index.js'
|
||||
import { modulosDeRol, type Modulo } from '../config/roles.js'
|
||||
import { AppError } from '../utils/errors.js'
|
||||
import * as configService from './config.service.js'
|
||||
|
||||
export interface UsuarioPublico {
|
||||
nombre: string
|
||||
@@ -14,6 +15,8 @@ export interface UsuarioPublico {
|
||||
mondayUserId: number | null
|
||||
// Módulos del ecosistema visibles para el rol (matriz de config/roles.ts)
|
||||
modulos: Modulo[]
|
||||
// Acciones granulares del rol (catálogo de config/acciones.ts)
|
||||
acciones: string[]
|
||||
}
|
||||
|
||||
export interface LoginResult {
|
||||
@@ -34,13 +37,23 @@ export function normalizarUsername(nombre: string): string {
|
||||
.replace(/\s+/g, ' ')
|
||||
}
|
||||
|
||||
export function aPublico(usuario: Usuario): UsuarioPublico {
|
||||
export async function aPublico(usuario: Usuario): Promise<UsuarioPublico> {
|
||||
let acciones: string[] = []
|
||||
try {
|
||||
const acc = await configService.obtenerAcciones(usuario.rol)
|
||||
acciones = Object.entries(acc)
|
||||
.filter(([, v]) => v)
|
||||
.map(([k]) => k)
|
||||
} catch {
|
||||
// Si falla la lectura de config, devuelve acciones vacías (no rompe)
|
||||
}
|
||||
return {
|
||||
nombre: usuario.nombre,
|
||||
rol: usuario.rol,
|
||||
asignacion: usuario.asignacion,
|
||||
mondayUserId: usuario.mondayUserId,
|
||||
modulos: modulosDeRol(usuario.rol),
|
||||
modulos: await modulosDeRol(usuario.rol),
|
||||
acciones,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +77,7 @@ export async function login(username: string, password: string): Promise<LoginRe
|
||||
config.auth.jwtSecret,
|
||||
{ expiresIn: config.auth.jwtExpiresIn } as jwt.SignOptions,
|
||||
)
|
||||
return { token, usuario: aPublico(usuario) }
|
||||
return { token, usuario: await aPublico(usuario) }
|
||||
}
|
||||
|
||||
/** Payload del JWT emitido por login() */
|
||||
|
||||
235
api-gateway/src/services/config.service.test.ts
Normal file
235
api-gateway/src/services/config.service.test.ts
Normal file
@@ -0,0 +1,235 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
// ---------- Prisma mockeado ----------
|
||||
const prismaMock = vi.hoisted(() => ({
|
||||
configuracionSistema: {
|
||||
findMany: vi.fn(),
|
||||
upsert: vi.fn(),
|
||||
},
|
||||
rolConfiguracion: {
|
||||
findMany: vi.fn(),
|
||||
findUnique: vi.fn(),
|
||||
upsert: vi.fn(),
|
||||
create: vi.fn(),
|
||||
deleteMany: vi.fn(),
|
||||
},
|
||||
usuario: {
|
||||
findMany: vi.fn(),
|
||||
update: vi.fn(),
|
||||
},
|
||||
$transaction: vi.fn(async (ops: Promise<unknown>[]) => Promise.all(ops)),
|
||||
}))
|
||||
|
||||
vi.mock('./prisma.js', () => ({ prisma: prismaMock }))
|
||||
|
||||
import {
|
||||
obtenerConfigs,
|
||||
actualizarConfigs,
|
||||
obtenerConfig,
|
||||
obtenerConfigSync,
|
||||
refrescarCache,
|
||||
resetCacheForTesting,
|
||||
obtenerRolesConfig,
|
||||
obtenerRolConfig,
|
||||
actualizarRolConfig,
|
||||
resetRoles,
|
||||
obtenerAcciones,
|
||||
obtenerUsuarios,
|
||||
actualizarUsuario,
|
||||
} from './config.service.js'
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
resetCacheForTesting()
|
||||
})
|
||||
|
||||
// ── ConfiguracionSistema ───────────────────────────────────────────────
|
||||
|
||||
describe('obtenerConfigs', () => {
|
||||
it('devuelve todas las configs como Record<clave, valor>', async () => {
|
||||
prismaMock.configuracionSistema.findMany.mockResolvedValue([
|
||||
{ clave: 'a', valor: '1', updatedAt: new Date() },
|
||||
{ clave: 'b', valor: '2', updatedAt: new Date() },
|
||||
])
|
||||
const result = await obtenerConfigs()
|
||||
expect(result).toEqual({ a: '1', b: '2' })
|
||||
})
|
||||
|
||||
it('devuelve objeto vacío si no hay configs', async () => {
|
||||
prismaMock.configuracionSistema.findMany.mockResolvedValue([])
|
||||
expect(await obtenerConfigs()).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
describe('actualizarConfigs', () => {
|
||||
it('upsert cada clave y refresca el cache', async () => {
|
||||
prismaMock.configuracionSistema.findMany.mockResolvedValue([])
|
||||
prismaMock.configuracionSistema.upsert.mockResolvedValue({})
|
||||
await actualizarConfigs({ x: '1', y: '2' })
|
||||
expect(prismaMock.configuracionSistema.upsert).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('obtenerConfig + obtenerConfigSync (cache)', () => {
|
||||
it('obtenerConfig consulta DB en primera llamada y cachea', async () => {
|
||||
prismaMock.configuracionSistema.findMany.mockResolvedValue([
|
||||
{ clave: 'cache_k', valor: 'cache_v', updatedAt: new Date() },
|
||||
])
|
||||
const callsBefore = prismaMock.configuracionSistema.findMany.mock.calls.length
|
||||
const v1 = await obtenerConfig('cache_k')
|
||||
expect(v1).toBe('cache_v')
|
||||
expect(prismaMock.configuracionSistema.findMany.mock.calls.length).toBe(callsBefore + 1)
|
||||
|
||||
// Segunda llamada usa cache (no vuelve a consultar DB)
|
||||
const v2 = await obtenerConfig('cache_k')
|
||||
expect(v2).toBe('cache_v')
|
||||
expect(prismaMock.configuracionSistema.findMany.mock.calls.length).toBe(callsBefore + 1)
|
||||
})
|
||||
|
||||
it('obtenerConfigSync lee del cache si ya fue inicializado', async () => {
|
||||
prismaMock.configuracionSistema.findMany.mockResolvedValue([
|
||||
{ clave: 'sync_k', valor: 'sync_v', updatedAt: new Date() },
|
||||
])
|
||||
await refrescarCache()
|
||||
expect(obtenerConfigSync('sync_k')).toBe('sync_v')
|
||||
})
|
||||
|
||||
it('refrescarCache recarga desde DB', async () => {
|
||||
prismaMock.configuracionSistema.findMany.mockResolvedValue([
|
||||
{ clave: 'nuevo', valor: 'si', updatedAt: new Date() },
|
||||
])
|
||||
await refrescarCache()
|
||||
expect(obtenerConfigSync('nuevo')).toBe('si')
|
||||
})
|
||||
})
|
||||
|
||||
// ── RolConfiguracion ───────────────────────────────────────────────────
|
||||
|
||||
describe('obtenerRolesConfig', () => {
|
||||
it('devuelve todos los roles ordenados', async () => {
|
||||
prismaMock.rolConfiguracion.findMany.mockResolvedValue([
|
||||
{ rol: 'admin', modulos: '[]', acciones: '{}', updatedAt: new Date() },
|
||||
])
|
||||
const result = await obtenerRolesConfig()
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0]!.rol).toBe('admin')
|
||||
})
|
||||
})
|
||||
|
||||
describe('obtenerRolConfig', () => {
|
||||
it('parsea JSON de modulos y acciones', async () => {
|
||||
prismaMock.rolConfiguracion.findUnique.mockResolvedValue({
|
||||
rol: 'vendedor',
|
||||
modulos: JSON.stringify(['mi-dia', 'cotizador']),
|
||||
acciones: JSON.stringify({ descartar_lead: true }),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
const result = await obtenerRolConfig('vendedor')
|
||||
expect(result).toEqual({
|
||||
modulos: ['mi-dia', 'cotizador'],
|
||||
acciones: { descartar_lead: true },
|
||||
})
|
||||
})
|
||||
|
||||
it('devuelve null si el rol no existe', async () => {
|
||||
prismaMock.rolConfiguracion.findUnique.mockResolvedValue(null)
|
||||
expect(await obtenerRolConfig('inexistente')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('actualizarRolConfig', () => {
|
||||
it('upsert con JSON serializado', async () => {
|
||||
prismaMock.rolConfiguracion.upsert.mockResolvedValue({})
|
||||
await actualizarRolConfig('admin', {
|
||||
modulos: ['all'],
|
||||
acciones: { todo: true },
|
||||
})
|
||||
expect(prismaMock.rolConfiguracion.upsert).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
where: { rol: 'admin' },
|
||||
create: expect.objectContaining({
|
||||
modulos: JSON.stringify(['all']),
|
||||
acciones: JSON.stringify({ todo: true }),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('resetRoles', () => {
|
||||
it('borra todas las configs y re-siembra desde MATRIZ + defaults', async () => {
|
||||
prismaMock.rolConfiguracion.deleteMany.mockResolvedValue({ count: 0 })
|
||||
prismaMock.rolConfiguracion.create.mockResolvedValue({})
|
||||
await resetRoles()
|
||||
expect(prismaMock.rolConfiguracion.deleteMany).toHaveBeenCalled()
|
||||
// MATRIZ tiene 6 roles
|
||||
expect(prismaMock.rolConfiguracion.create).toHaveBeenCalledTimes(6)
|
||||
})
|
||||
})
|
||||
|
||||
// ── Acciones por rol ───────────────────────────────────────────────────
|
||||
|
||||
describe('obtenerAcciones', () => {
|
||||
it('lee de DB si existe el rol', async () => {
|
||||
prismaMock.rolConfiguracion.findUnique.mockResolvedValue({
|
||||
rol: 'vendedor',
|
||||
modulos: '[]',
|
||||
acciones: JSON.stringify({ descartar_lead: true, ver_reportes: false }),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
const acc = await obtenerAcciones('vendedor')
|
||||
expect(acc.descartar_lead).toBe(true)
|
||||
expect(acc.ver_reportes).toBe(false)
|
||||
})
|
||||
|
||||
it('fallback a defaults si el rol no existe en DB', async () => {
|
||||
prismaMock.rolConfiguracion.findUnique.mockResolvedValue(null)
|
||||
const acc = await obtenerAcciones('vendedor')
|
||||
expect(acc.descartar_lead).toBe(true)
|
||||
expect(acc.ver_reportes).toBe(false)
|
||||
})
|
||||
|
||||
it('fallback a defaults si JSON está corrupto', async () => {
|
||||
prismaMock.rolConfiguracion.findUnique.mockResolvedValue({
|
||||
rol: 'vendedor',
|
||||
modulos: '[]',
|
||||
acciones: 'no-json',
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
const acc = await obtenerAcciones('vendedor')
|
||||
expect(acc.descartar_lead).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
// ── Usuarios ───────────────────────────────────────────────────────────
|
||||
|
||||
describe('obtenerUsuarios', () => {
|
||||
it('devuelve usuarios sin passwordHash', async () => {
|
||||
prismaMock.usuario.findMany.mockResolvedValue([
|
||||
{ id: 'u1', username: 'a', nombre: 'A', rol: 'vendedor', asignacion: null, activo: true, mondayUserId: null },
|
||||
])
|
||||
const result = await obtenerUsuarios()
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0]).not.toHaveProperty('passwordHash')
|
||||
})
|
||||
})
|
||||
|
||||
describe('actualizarUsuario', () => {
|
||||
it('actualiza solo los campos enviados', async () => {
|
||||
prismaMock.usuario.update.mockResolvedValue({})
|
||||
await actualizarUsuario('u1', { rol: 'admin' })
|
||||
expect(prismaMock.usuario.update).toHaveBeenCalledWith({
|
||||
where: { id: 'u1' },
|
||||
data: { rol: 'admin' },
|
||||
})
|
||||
})
|
||||
|
||||
it('actualiza asignacion y activo', async () => {
|
||||
prismaMock.usuario.update.mockResolvedValue({})
|
||||
await actualizarUsuario('u1', { asignacion: null, activo: false })
|
||||
expect(prismaMock.usuario.update).toHaveBeenCalledWith({
|
||||
where: { id: 'u1' },
|
||||
data: { asignacion: null, activo: false },
|
||||
})
|
||||
})
|
||||
})
|
||||
194
api-gateway/src/services/config.service.ts
Normal file
194
api-gateway/src/services/config.service.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* Servicio de configuración del sistema.
|
||||
*
|
||||
* Lee/escribe las tablas ConfiguracionSistema y RolConfiguracion, y opera
|
||||
* sobre usuarios (rol, asignación, activo). Singleton con cache de 30 s en
|
||||
* `obtenerConfig` para no golpear la DB en cada request.
|
||||
*/
|
||||
|
||||
import { prisma } from './prisma.js'
|
||||
import { accionesPorRol, type AccionClave } from '../config/acciones.js'
|
||||
import { MATRIZ } from '../config/roles.js'
|
||||
import type { Modulo } from '../config/roles.js'
|
||||
|
||||
// ── Cache de 30 s para obtenerConfig ────────────────────────────────────
|
||||
const CACHE_TTL_MS = 30_000
|
||||
let cacheMap: Record<string, string> | null = null
|
||||
let cacheExpiresAt = 0
|
||||
|
||||
function invalidarCache(): void {
|
||||
cacheMap = null
|
||||
cacheExpiresAt = 0
|
||||
}
|
||||
|
||||
// ── ConfiguracionSistema ───────────────────────────────────────────────
|
||||
|
||||
/** Todas las configs como Record<clave, valor> */
|
||||
export async function obtenerConfigs(): Promise<Record<string, string>> {
|
||||
const rows = await prisma.configuracionSistema.findMany()
|
||||
const result: Record<string, string> = {}
|
||||
for (const r of rows) result[r.clave] = r.valor
|
||||
return result
|
||||
}
|
||||
|
||||
/** Upsert parcial de configs (solo las claves enviadas) */
|
||||
export async function actualizarConfigs(parcial: Record<string, string>): Promise<void> {
|
||||
await prisma.$transaction(
|
||||
Object.entries(parcial).map(([clave, valor]) =>
|
||||
prisma.configuracionSistema.upsert({
|
||||
where: { clave },
|
||||
create: { clave, valor },
|
||||
update: { valor },
|
||||
}),
|
||||
),
|
||||
)
|
||||
await refrescarCache()
|
||||
}
|
||||
|
||||
/** Una config individual (cache 30 s). Devuelve null si la clave no existe. */
|
||||
export async function obtenerConfig(clave: string): Promise<string | null> {
|
||||
const now = Date.now()
|
||||
if (cacheMap && now < cacheExpiresAt) return cacheMap[clave] ?? null
|
||||
|
||||
cacheMap = await obtenerConfigs()
|
||||
cacheExpiresAt = now + CACHE_TTL_MS
|
||||
return cacheMap[clave] ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
* Versión síncrona de obtenerConfig: lee del cache en memoria.
|
||||
* Devuelve undefined si el cache no ha sido inicializado aún (el caller
|
||||
* debe usar el fallback apropiado, p. ej. process.env).
|
||||
* Se alimenta con `refrescarCache()` (llamar en startup y al cambiar configs).
|
||||
*/
|
||||
export function obtenerConfigSync(clave: string): string | undefined {
|
||||
if (!cacheMap) return undefined
|
||||
return cacheMap[clave]
|
||||
}
|
||||
|
||||
/** Fuerza la recarga del cache desde la DB (llamar en startup y al guardar configs) */
|
||||
export async function refrescarCache(): Promise<void> {
|
||||
cacheMap = await obtenerConfigs()
|
||||
cacheExpiresAt = Date.now() + CACHE_TTL_MS
|
||||
}
|
||||
|
||||
/** Resetea el cache (para tests) */
|
||||
export function resetCacheForTesting(): void {
|
||||
cacheMap = null
|
||||
cacheExpiresAt = 0
|
||||
}
|
||||
|
||||
// ── RolConfiguracion ───────────────────────────────────────────────────
|
||||
|
||||
export interface RolConfigData {
|
||||
modulos: string[]
|
||||
acciones: Record<string, boolean>
|
||||
}
|
||||
|
||||
/** Todas las configs de rol (sin parsear JSON) */
|
||||
export async function obtenerRolesConfig() {
|
||||
return prisma.rolConfiguracion.findMany({ orderBy: { rol: 'asc' } })
|
||||
}
|
||||
|
||||
/** Config de un rol (JSON parseado) */
|
||||
export async function obtenerRolConfig(rol: string): Promise<RolConfigData | null> {
|
||||
const row = await prisma.rolConfiguracion.findUnique({ where: { rol } })
|
||||
if (!row) return null
|
||||
return {
|
||||
modulos: JSON.parse(row.modulos) as string[],
|
||||
acciones: JSON.parse(row.acciones) as Record<string, boolean>,
|
||||
}
|
||||
}
|
||||
|
||||
/** Upsert de módulos y acciones de un rol */
|
||||
export async function actualizarRolConfig(
|
||||
rol: string,
|
||||
data: { modulos: string[]; acciones: Record<string, boolean> },
|
||||
): Promise<void> {
|
||||
await prisma.rolConfiguracion.upsert({
|
||||
where: { rol },
|
||||
create: {
|
||||
rol,
|
||||
modulos: JSON.stringify(data.modulos),
|
||||
acciones: JSON.stringify(data.acciones),
|
||||
},
|
||||
update: {
|
||||
modulos: JSON.stringify(data.modulos),
|
||||
acciones: JSON.stringify(data.acciones),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/** Borra todas las RolConfiguracion y re-siembra desde MATRIZ + ACCIONES_POR_ROL_DEFAULT */
|
||||
export async function resetRoles(): Promise<void> {
|
||||
await prisma.rolConfiguracion.deleteMany()
|
||||
|
||||
const entries = Object.entries(MATRIZ) as [string, readonly Modulo[]][]
|
||||
await prisma.$transaction(
|
||||
entries.map(([rol, modulos]) => {
|
||||
const acciones = accionesPorRol(rol)
|
||||
return prisma.rolConfiguracion.create({
|
||||
data: {
|
||||
rol,
|
||||
modulos: JSON.stringify([...modulos]),
|
||||
acciones: JSON.stringify(acciones),
|
||||
},
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// ── Acciones por rol ───────────────────────────────────────────────────
|
||||
|
||||
/** Las acciones booleanas de un rol (lee de DB; fallback a defaults) */
|
||||
export async function obtenerAcciones(rol: string): Promise<Record<AccionClave, boolean>> {
|
||||
const row = await prisma.rolConfiguracion.findUnique({ where: { rol } })
|
||||
if (!row) return accionesPorRol(rol)
|
||||
try {
|
||||
return JSON.parse(row.acciones) as Record<AccionClave, boolean>
|
||||
} catch {
|
||||
return accionesPorRol(rol)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Usuarios ───────────────────────────────────────────────────────────
|
||||
|
||||
export interface UsuarioListado {
|
||||
id: string
|
||||
username: string
|
||||
nombre: string
|
||||
rol: string
|
||||
asignacion: string | null
|
||||
activo: boolean
|
||||
mondayUserId: number | null
|
||||
}
|
||||
|
||||
/** Lista de usuarios sin passwordHash */
|
||||
export async function obtenerUsuarios(): Promise<UsuarioListado[]> {
|
||||
const rows = await prisma.usuario.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
nombre: true,
|
||||
rol: true,
|
||||
asignacion: true,
|
||||
activo: true,
|
||||
mondayUserId: true,
|
||||
},
|
||||
orderBy: { nombre: 'asc' },
|
||||
})
|
||||
return rows
|
||||
}
|
||||
|
||||
/** Actualiza rol, asignación y/o activo de un usuario */
|
||||
export async function actualizarUsuario(
|
||||
id: string,
|
||||
data: { rol?: string; asignacion?: string | null; activo?: boolean },
|
||||
): Promise<void> {
|
||||
// Filtra undefined para que Prisma solo actualice los campos enviados
|
||||
const clean: Record<string, unknown> = {}
|
||||
if (data.rol !== undefined) clean.rol = data.rol
|
||||
if (data.asignacion !== undefined) clean.asignacion = data.asignacion
|
||||
if (data.activo !== undefined) clean.activo = data.activo
|
||||
await prisma.usuario.update({ where: { id }, data: clean as never })
|
||||
}
|
||||
@@ -7,7 +7,8 @@
|
||||
* local) en lugar de pagar los reintentos por request; un cron sondea cada 60 s
|
||||
* y al recuperarse dispara el sync worker.
|
||||
*
|
||||
* MODO_LOCAL_FORZADO=true fuerza modo local aunque Monday responda (pruebas).
|
||||
* MODO_LOCAL_FORZADO=true (env var o DB) fuerza modo local aunque Monday
|
||||
* responda (pruebas / toggle admin). La DB se cachea 30 s en configService.
|
||||
*
|
||||
* El ping se inyecta con `registrarPing()` (wiring en index.ts) para no
|
||||
* importar monday.service aquí y evitar ciclos de imports.
|
||||
@@ -45,9 +46,24 @@ export class SaludMondayService {
|
||||
return this.mondayOk
|
||||
}
|
||||
|
||||
/** true si los callers pueden hablar con Monday ahora mismo */
|
||||
/**
|
||||
* true si los callers pueden hablar con Monday ahora mismo.
|
||||
* Lee modo_local_forzado de la DB (via cache 30 s en configService) y de
|
||||
* process.env como fallback. Síncrono: la DB se precarga al iniciar y se
|
||||
* invalida al cambiar configs.
|
||||
*/
|
||||
estaDisponible(): boolean {
|
||||
return this.mondayOk && process.env.MODO_LOCAL_FORZADO !== 'true'
|
||||
// Lazy import para no crear ciclos en tests (configService importa prisma)
|
||||
let forzadoDb = false
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const cs = require('./config.service.js') as typeof import('./config.service.js')
|
||||
forzadoDb = cs.obtenerConfigSync('modo_local_forzado') === 'true'
|
||||
} catch {
|
||||
// configService no disponible (tests aislados, DB no inicializada)
|
||||
}
|
||||
const forzadoEnv = process.env.MODO_LOCAL_FORZADO === 'true'
|
||||
return this.mondayOk && !forzadoDb && !forzadoEnv
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user