import { ForbiddenException, NotFoundException } from "@nestjs/common";
import { FLAGS_UI, entradaDe } from "./taller-parametros.catalogo";
import { TallerParametrosService } from "./taller-parametros.service";

function createService(overrides: Record<string, any> = {}) {
  const filas = new Map<string, any>();

  const repo = {
    findOne: jest.fn(async ({ where }: any) => {
      const key = `${where.clave}:${where.tiendaId}:${where.destino}`;
      return filas.get(key) ?? null;
    }),
    // In(...) de typeorm llega como FindOperator con .value
    find: jest.fn(async ({ where }: any) => {
      const claves: string[] = where.clave.value ?? [where.clave];
      return claves
        .map((c) => filas.get(`${c}:${where.tiendaId}:${where.destino}`))
        .filter(Boolean);
    }),
    create: jest.fn((data: any) => data),
    save: jest.fn(async (data: any) => {
      const saved = { id: filas.size + 1, ...data };
      filas.set(`${data.clave}:${data.tiendaId}:${data.destino}`, saved);
      return saved;
    }),
    update: jest.fn(async (id: number, data: any) => {
      for (const [key, fila] of filas.entries()) {
        if (fila.id === id) filas.set(key, { ...fila, ...data });
      }
    }),
    ...overrides.repo,
  };

  // Repo SEPARADO del de arriba a propósito, aunque esta tarea todavía no lo
  // usa (llega en Task 5): repositoryFor() diferencia por entidad desde el
  // principio para que agregar historial más adelante no pise silenciosamente
  // las filas de `repo` -- ambos usan la misma forma de clave interna
  // (clave:tiendaId:destino) y compartir un solo mock los mezclaría.
  const historialRepo = {
    create: jest.fn((data: any) => data),
    save: jest.fn(async (data: any) => ({ id: 1, ...data })),
    ...overrides.historialRepo,
  };

  const tenantAccessor = {
    getCurrentTenantId: jest.fn(() => 7),
    repositoryFor: jest.fn((entity: any) =>
      entity?.name === "TallerParametroHistorial" ? historialRepo : repo,
    ),
    ...overrides.tenantAccessor,
  };

  const credentialsCrypto = {
    encrypt: jest.fn((plain: string) => `ENC(${plain})`),
    decrypt: jest.fn((blob: string) => blob.replace(/^ENC\(|\)$/g, "")),
    ...overrides.credentialsCrypto,
  };

  // Default: tenant sin fila en tenant_config (null) = sitio nuevo, que cae
  // al default del catalogo (apagado) -- igual que TenantConfigService real.
  const tenantConfigService = {
    getTogglesOperativos: jest.fn(async () => null),
    ...overrides.tenantConfigService,
  };

  const service = new TallerParametrosService(
    tenantAccessor as any,
    credentialsCrypto as any,
    tenantConfigService as any,
    (overrides.tenantConnectionRegistry as any) ?? (undefined as any), // ver Task 4
  );

  return {
    service,
    repo,
    historialRepo,
    tenantAccessor,
    credentialsCrypto,
    filas,
  };
}

describe("TallerParametrosService — núcleo ambiental", () => {
  it("get() devuelve el default del catálogo cuando no hay fila", async () => {
    const { service } = createService();
    await expect(
      service.get("facturacion.exige_pago_para_entrega"),
    ).resolves.toBe(true);
    await expect(service.get("ot.usa_fotos")).resolves.toBe(false);
    await expect(
      service.get("notificaciones.whatsapp_api_version"),
    ).resolves.toBe("v25.0");
  });

  it("get() castea boolean/number/json correctamente desde texto guardado", async () => {
    const { service } = createService();
    await service.set("productos.permite_crear", false, 1);
    await expect(service.get("productos.permite_crear")).resolves.toBe(false);
  });

  it("getParaTienda() castea number", async () => {
    const { service } = createService();
    await service.setParaTienda(9, "facturacion_pos.precio_decimales", 3, 1);
    await expect(
      service.getParaTienda(9, "facturacion_pos.precio_decimales"),
    ).resolves.toBe(3);
  });

  it("claves sensibles se cifran al escribir y se descifran al leer, nunca en texto plano en `valor`", async () => {
    const { service, filas } = createService();
    await service.setParaTienda(9, "facturacion_pos.password", "secreta123", 1);
    const fila = filas.get("facturacion_pos.password:9:");
    expect(fila.valor).toBeNull();
    expect(fila.valorCifrado).toBe("ENC(secreta123)");
    await expect(
      service.getParaTienda(9, "facturacion_pos.password"),
    ).resolves.toBe("secreta123");
  });

  it("set() invalida la caché del tenant: una lectura posterior refleja el nuevo valor sin volver a golpear el repo si no cambió, y SÍ refleja el cambio tras escribir", async () => {
    const { service, repo } = createService();
    await service.get("facturacion.usa_facturacion"); // primera lectura, popula caché
    await service.get("facturacion.usa_facturacion"); // segunda lectura, debería venir de caché
    // 1: sin fila y sin tenant_config (sitio nuevo) la clave-puente cae al
    // default del catálogo y no dispara el respaldo hacia taller_parametros;
    // la SEGUNDA lectura viene de caché.
    expect(repo.findOne).toHaveBeenCalledTimes(1);

    await service.set("facturacion.usa_facturacion", false, 1);
    await expect(service.get("facturacion.usa_facturacion")).resolves.toBe(
      false,
    );
  });
});

function createRepoFake() {
  const filas = new Map<string, any>();
  return {
    filas,
    findOne: jest.fn(
      async ({ where }: any) =>
        filas.get(`${where.clave}:${where.tiendaId}:${where.destino}`) ?? null,
    ),
    create: jest.fn((d: any) => d),
    save: jest.fn(async (d: any) => {
      const saved = { id: filas.size + 1, ...d };
      filas.set(`${d.clave}:${d.tiendaId}:${d.destino}`, saved);
      return saved;
    }),
    update: jest.fn(async (id: number, data: any) => {
      for (const [k, f] of filas.entries())
        if (f.id === id) filas.set(k, { ...f, ...data });
    }),
  };
}

describe("TallerParametrosService — superficie explícita", () => {
  it("getExplicit() usa TenantConnectionRegistry.withTenantDataSourceExplicit, no el contexto ambiental", async () => {
    const parametrosRepo = createRepoFake();
    const historialRepo = createRepoFake();
    const dataSource = {
      getRepository: jest.fn((entity: any) =>
        entity?.name === "TallerParametroHistorial"
          ? historialRepo
          : parametrosRepo,
      ),
    };
    const tenantConnectionRegistry = {
      withTenantDataSourceExplicit: jest.fn(
        async (_tenantId: number, fn: any) => fn(dataSource),
      ),
      repositoryForDataSource: jest.fn((ds: any, entity: any) =>
        ds.getRepository(entity),
      ),
    };
    const { service, tenantAccessor } = createService({
      tenantConnectionRegistry,
    });

    await service.getExplicit(55, "tiendas.permite_crear");

    expect(
      tenantConnectionRegistry.withTenantDataSourceExplicit,
    ).toHaveBeenCalledWith(55, expect.any(Function), undefined);
    expect(tenantAccessor.repositoryFor).not.toHaveBeenCalled();
  });

  it("setExplicit() invalida la caché de ESE tenantId, no la del contexto ambiental", async () => {
    const parametrosRepo = createRepoFake();
    const historialRepo = createRepoFake();
    const dataSource = {
      getRepository: jest.fn((entity: any) =>
        entity?.name === "TallerParametroHistorial"
          ? historialRepo
          : parametrosRepo,
      ),
    };
    const tenantConnectionRegistry = {
      withTenantDataSourceExplicit: jest.fn(
        async (_tenantId: number, fn: any) => fn(dataSource),
      ),
      repositoryForDataSource: jest.fn((ds: any, entity: any) =>
        ds.getRepository(entity),
      ),
    };
    const { service } = createService({ tenantConnectionRegistry });

    await service.setExplicit(55, "tiendas.permite_crear", false, 1);
    await expect(
      service.getExplicit(55, "tiendas.permite_crear"),
    ).resolves.toBe(false);
  });
});

describe("TallerParametrosService — requeridoSi e historial", () => {
  it("rechaza activar facturacion_pos.habilitada sin los campos dependientes", async () => {
    const { service } = createService();
    await expect(
      service.setGrupoParaTienda(9, { "facturacion_pos.habilitada": true }, 1),
    ).rejects.toThrow(/es obligatorio porque/);
  });

  it("acepta activar facturacion_pos.habilitada cuando el grupo completo viene en la misma escritura", async () => {
    const { service } = createService();
    await expect(
      service.setGrupoParaTienda(
        9,
        {
          "facturacion_pos.habilitada": true,
          "facturacion_pos.url": "http://postouch/create_ordenes_edngt",
          "facturacion_pos.username": "22",
          "facturacion_pos.password": "secreta",
          "facturacion_pos.computadora": "5.5",
          "facturacion_pos.precio_decimales": 2,
        },
        1,
      ),
    ).resolves.toBeUndefined();
  });

  it("acepta reenviar solo un campo (ej. computadora) de una tienda YA configurada, sin repetir password", async () => {
    const { service } = createService();
    await service.setGrupoParaTienda(
      9,
      {
        "facturacion_pos.habilitada": true,
        "facturacion_pos.url": "http://postouch/create_ordenes_edngt",
        "facturacion_pos.username": "22",
        "facturacion_pos.password": "secreta",
        "facturacion_pos.computadora": "5.5",
        "facturacion_pos.precio_decimales": 2,
      },
      1,
    );

    await expect(
      service.setGrupoParaTienda(
        9,
        { "facturacion_pos.computadora": "9.1" },
        1,
      ),
    ).resolves.toBeUndefined();
    await expect(
      service.getParaTienda(9, "facturacion_pos.computadora"),
    ).resolves.toBe("9.1");
    await expect(
      service.getParaTienda(9, "facturacion_pos.password"),
    ).resolves.toBe("secreta");
  });

  it("desactivar facturacion_pos.habilitada NO exige los demás campos", async () => {
    const { service } = createService();
    await expect(
      service.setGrupoParaTienda(9, { "facturacion_pos.habilitada": false }, 1),
    ).resolves.toBeUndefined();
  });

  it("registra historial con valor anterior/nuevo para una clave NO sensible", async () => {
    const { service, historialRepo } = createService();
    await service.set("productos.permite_crear", true, 1);
    expect(historialRepo.save).toHaveBeenCalledWith(
      expect.objectContaining({
        clave: "productos.permite_crear",
        valorAnterior: "false",
        valorNuevo: "true",
        usuarioId: 1,
      }),
    );
  });

  it("NUNCA registra el valor real de una clave sensible en el historial", async () => {
    const { service, historialRepo } = createService();
    await service.setParaTienda(9, "facturacion_pos.password", "secreta123", 1);
    expect(historialRepo.save).toHaveBeenCalledWith(
      expect.objectContaining({
        clave: "facturacion_pos.password",
        valorAnterior: null,
        valorNuevo: null,
        usuarioId: 1,
      }),
    );
  });
});

describe("TallerParametrosService — puente de tenant_config para los 3 toggles", () => {
  it("get() de una clave-puente sin fila usa tenant_config y la sirve de inmediato", async () => {
    const tenantConfigService = {
      getTogglesOperativos: jest.fn(async () => ({
        permiteTiendas: false,
        permiteProductosPropios: true,
        facturaHabilitada: true,
      })),
    };
    const { service } = createService({ tenantConfigService });

    await expect(service.get("tiendas.permite_crear")).resolves.toBe(false);
    expect(tenantConfigService.getTogglesOperativos).toHaveBeenCalledWith(7);
  });

  it("tenant SIN fila en tenant_config (sitio nuevo): cae al default apagado del catálogo y no persiste nada", async () => {
    const { service, repo } = createService();

    await expect(service.get("tiendas.permite_crear")).resolves.toBe(false);
    await expect(service.get("productos.permite_crear")).resolves.toBe(false);
    await expect(service.get("facturacion.usa_facturacion")).resolves.toBe(
      false,
    );
    await new Promise((resolve) => setImmediate(resolve));

    expect(repo.save).not.toHaveBeenCalled();
  });

  it("intenta persistir la copia hacia taller_parametros, sin bloquear la respuesta", async () => {
    const tenantConfigService = {
      getTogglesOperativos: jest.fn(async () => ({
        permiteTiendas: false,
        permiteProductosPropios: true,
        facturaHabilitada: true,
      })),
    };
    const { service, repo } = createService({ tenantConfigService });

    await service.get("tiendas.permite_crear");
    await new Promise((resolve) => setImmediate(resolve)); // deja correr el fire-and-forget

    expect(repo.save).toHaveBeenCalled();
  });

  it("si la persistencia de respaldo falla, igual devuelve el valor correcto y no rompe la promesa", async () => {
    const tenantConfigService = {
      getTogglesOperativos: jest.fn(async () => ({
        permiteTiendas: true,
        permiteProductosPropios: true,
        facturaHabilitada: true,
      })),
    };
    const repoOverride = {
      save: jest.fn(async () => {
        throw new Error("DB caída");
      }),
    };
    const { service } = createService({
      tenantConfigService,
      repo: repoOverride,
    });

    await expect(service.get("tiendas.permite_crear")).resolves.toBe(true);
    await new Promise((resolve) => setImmediate(resolve)); // deja correr el .catch() interno
  });

  it("una segunda lectura del mismo tenant NO vuelve a consultar tenant_config -- sirve desde caché", async () => {
    const tenantConfigService = {
      getTogglesOperativos: jest.fn(async () => ({
        permiteTiendas: false,
        permiteProductosPropios: true,
        facturaHabilitada: true,
      })),
    };
    const { service } = createService({ tenantConfigService });

    await service.get("tiendas.permite_crear");
    await service.get("tiendas.permite_crear");

    expect(tenantConfigService.getTogglesOperativos).toHaveBeenCalledTimes(1);
  });

  it("una clave-puente CON fila ya guardada nunca toca tenant_config", async () => {
    const tenantConfigService = { getTogglesOperativos: jest.fn() };
    const { service } = createService({ tenantConfigService });

    await service.set("tiendas.permite_crear", true, 1);
    tenantConfigService.getTogglesOperativos.mockClear();

    await expect(service.get("tiendas.permite_crear")).resolves.toBe(true);
    expect(tenantConfigService.getTogglesOperativos).not.toHaveBeenCalled();
  });
});

describe("TallerParametrosService.getResumenTenant", () => {
  it("enmascara claves sensibles como {configurado:boolean}, nunca el valor real", async () => {
    const { service, repo, tenantAccessor } = createService();
    const tiendaRepo = {
      find: jest.fn(async () => [{ id: 3, nombre: "Tienda 3" }]),
    };
    tenantAccessor.repositoryFor.mockImplementation((entity: any) =>
      entity?.name === "Tienda"
        ? tiendaRepo
        : entity?.name === "TallerParametroHistorial"
          ? { create: jest.fn(), save: jest.fn() }
          : repo,
    );

    await service.setParaTienda(3, "facturacion_pos.password", "secreta", 1);

    const resumen = await service.getResumenTenant();

    expect(resumen.tiendas).toEqual([
      expect.objectContaining({
        tiendaId: 3,
        nombre: "Tienda 3",
        parametros: expect.objectContaining({
          "facturacion_pos.password": { configurado: true },
        }),
      }),
    ]);
  });

  it("incluye todas las claves tenant-scope con su valor (o default) tipado", async () => {
    const { service, tenantAccessor } = createService();
    // Captura la implementacion ORIGINAL antes de reemplazarla -- llamar
    // getMockImplementation() DENTRO de la nueva implementacion se
    // autorreferencia (devuelve la funcion que se esta definiendo, no la de
    // createService()) y recursiona infinito para cualquier entidad que no
    // sea Tienda.
    const original = tenantAccessor.repositoryFor.getMockImplementation()!;
    tenantAccessor.repositoryFor.mockImplementation((entity: any) =>
      entity?.name === "Tienda"
        ? { find: jest.fn(async () => []) }
        : original(entity),
    );

    const resumen = await service.getResumenTenant();

    expect(resumen.tenant["facturacion.usa_facturacion"]).toBe(false);
    expect(resumen.tenant["facturacion.exige_pago_para_entrega"]).toBe(true);
    expect(resumen.tenant["ot.usa_fotos"]).toBe(false);
    expect(resumen.tenant["notificaciones.usa_whatsapp"]).toBe(false);
  });
});

describe("TallerParametrosService — comportamiento ante fallo", () => {
  it("getBooleanConFallback devuelve false (bloquear) para un gate que falla y bloquea por default", async () => {
    const repoQueFalla = {
      findOne: jest.fn(async () => {
        throw new Error("DB caída");
      }),
    };
    const { service } = createService({ repo: repoQueFalla });
    await expect(
      service.getBooleanConFallback("tiendas.permite_crear"),
    ).resolves.toBe(false);
  });

  it("getBooleanConFallback devuelve true (permitir) para un gate que falla y permite por default", async () => {
    const repoQueFalla = {
      findOne: jest.fn(async () => {
        throw new Error("DB caída");
      }),
    };
    const { service } = createService({ repo: repoQueFalla });
    await expect(
      service.getBooleanConFallback("productos.permite_crear"),
    ).resolves.toBe(true);
  });

  it("getFlags() nunca lanza -- cada clave aplica su propio comportamientoAnteFallo si falla", async () => {
    const repoQueFalla = {
      findOne: jest.fn(async () => {
        throw new Error("DB caída");
      }),
      find: jest.fn(async () => {
        throw new Error("DB caída");
      }),
    };
    const { service } = createService({ repo: repoQueFalla });

    const flags = await service.getFlags();

    expect(Object.keys(flags).sort()).toEqual(Object.keys(FLAGS_UI).sort());
    for (const [flag, clave] of Object.entries(FLAGS_UI)) {
      const esperado = entradaDe(clave).comportamientoAnteFallo === "permitir";
      expect([flag, flags[flag as keyof typeof flags]]).toEqual([
        flag,
        esperado,
      ]);
    }
    expect(flags.usaFacturacion).toBe(false);
    expect(flags.permiteProductosPropios).toBe(true);
    expect(flags.usaFotos).toBe(true);
  });

  it("getFlags() en un sitio nuevo: todo apagado salvo exigePagoParaEntrega", async () => {
    const { service } = createService();

    const flags = await service.getFlags();

    const encendidos = Object.entries(flags)
      .filter(([, v]) => v)
      .map(([k]) => k);
    expect(encendidos).toEqual(["exigePagoParaEntrega"]);
  });

  it("getFlags() en condiciones normales refleja los valores guardados", async () => {
    const { service } = createService();
    await service.set("facturacion.usa_facturacion", true, 1);
    await service.set("ot.usa_fotos", true, 1);

    const flags = await service.getFlags();

    expect(flags.usaFacturacion).toBe(true);
    expect(flags.usaFotos).toBe(true);
    expect(flags.usaDanos).toBe(false);
    expect(flags.usaWhatsapp).toBe(false);
  });

  it("getFlags() hace UNA sola consulta de lectura y la segunda llamada sale de caché", async () => {
    const { service, repo } = createService();

    await service.getFlags();
    expect(repo.find).toHaveBeenCalledTimes(1);
    // solo las 3 claves-puente sin fila consultan tenant_config; nada de findOne por clave
    expect(repo.findOne).toHaveBeenCalledTimes(3);

    repo.find.mockClear();
    repo.findOne.mockClear();
    await service.getFlags();
    expect(repo.find).not.toHaveBeenCalled();
    expect(repo.findOne).not.toHaveBeenCalled();
  });

  it("getFlags() respeta tenant_config para una clave-puente cuando el tenant ya tenía fila allí", async () => {
    const tenantConfigService = {
      getTogglesOperativos: jest.fn(async () => ({
        permiteTiendas: true,
        permiteProductosPropios: false,
        facturaHabilitada: true,
      })),
    };
    const { service } = createService({ tenantConfigService });

    const flags = await service.getFlags();

    expect(flags.permiteTiendas).toBe(true);
    expect(flags.permiteProductosPropios).toBe(false);
    expect(flags.usaFacturacion).toBe(true);
  });

  it("getTogglesOperativosAdmin devuelve null por clave (no tumba las otras) si la tabla no existe todavía", async () => {
    const repoQueFalla = {
      findOne: jest.fn(async () => {
        throw new Error("Table 'taller_parametros' doesn't exist");
      }),
    };
    const dataSource = { getRepository: jest.fn(() => repoQueFalla) };
    const tenantConnectionRegistry = {
      withTenantDataSourceExplicit: jest.fn(
        async (_tenantId: number, fn: any) => fn(dataSource),
      ),
    };
    const { service } = createService({ tenantConnectionRegistry });

    await expect(service.getTogglesOperativosAdmin(99)).resolves.toEqual({
      permiteTiendas: null,
      permiteProductosPropios: null,
      facturaHabilitada: null,
    });
  });

  it("getTogglesOperativosAdmin pide allowOnboarding:true", async () => {
    const repoOk = { findOne: jest.fn(async () => null) };
    const dataSource = { getRepository: jest.fn(() => repoOk) };
    const tenantConnectionRegistry = {
      withTenantDataSourceExplicit: jest.fn(
        async (_tenantId: number, fn: any) => fn(dataSource),
      ),
    };
    const { service } = createService({ tenantConnectionRegistry });

    await service.getTogglesOperativosAdmin(99);

    expect(
      tenantConnectionRegistry.withTenantDataSourceExplicit,
    ).toHaveBeenCalledWith(99, expect.any(Function), {
      allowOnboarding: true,
    });
  });
});

describe("TallerParametrosService — superficie explícita para el panel de plataforma", () => {
  function conRegistry(repo: any, historialRepo: any) {
    const dataSource = {};
    return {
      withTenantDataSourceExplicit: jest.fn(
        async (_tenantId: number, fn: any) => fn(dataSource),
      ),
      repositoryForDataSource: jest.fn((_ds: any, entity: any) =>
        entity?.name === "TallerParametroHistorial" ? historialRepo : repo,
      ),
    };
  }

  it("setGrupoExplicit escribe varias claves y registra historial de cada una", async () => {
    const historialRepo = {
      create: jest.fn((d: any) => d),
      save: jest.fn(async (d: any) => d),
    };
    const repo = createRepoFake();
    const registry = conRegistry(repo, historialRepo);
    const { service } = createService({ tenantConnectionRegistry: registry });

    await service.setGrupoExplicit(
      5,
      { "ot.usa_fotos": true, "ot.usa_danos": true },
      0,
      { allowOnboarding: true },
    );

    expect(registry.withTenantDataSourceExplicit).toHaveBeenCalledWith(
      5,
      expect.any(Function),
      { allowOnboarding: true },
    );
    expect(repo.filas.get("ot.usa_fotos:0:")?.valor).toBe("true");
    expect(repo.filas.get("ot.usa_danos:0:")?.valor).toBe("true");
    expect(historialRepo.save).toHaveBeenCalledTimes(2);
  });

  it("getBooleanosTenantExplicit devuelve solo claves booleanas de ámbito tenant", async () => {
    const repo = createRepoFake();
    const registry = conRegistry(repo, {});
    const { service } = createService({ tenantConnectionRegistry: registry });

    const valores = await service.getBooleanosTenantExplicit(5, {
      allowOnboarding: true,
    });

    expect(valores["ot.usa_fotos"]).toBe(false);
    expect(valores["facturacion.exige_pago_para_entrega"]).toBe(true);
    expect(valores).not.toHaveProperty(["notificaciones.whatsapp_api_version"]);
    expect(valores).not.toHaveProperty(["facturacion_pos.habilitada"]);
  });
});

describe("TallerParametrosService.exigirHabilitado", () => {
  function conBooleano(valor: boolean | Error) {
    const servicio = Object.create(TallerParametrosService.prototype) as any;
    servicio.logger = { warn: jest.fn() };
    servicio.get = jest.fn(async () => {
      if (valor instanceof Error) throw valor;
      return valor;
    });
    return servicio as TallerParametrosService;
  }

  it("no hace nada si la funcion esta encendida", async () => {
    await expect(conBooleano(true).exigirHabilitado("ot.usa_fotos")).resolves.toBeUndefined();
  });

  it("lanza 403 con la etiqueta de la funcion y a donde ir a activarla", async () => {
    const promesa = conBooleano(false).exigirHabilitado("ot.usa_fotos");

    await expect(promesa).rejects.toBeInstanceOf(ForbiddenException);
    await expect(promesa).rejects.toThrow(/no está habilitado para este taller.*Configuración → Operación y módulos/);
  });

  it("con comoNoEncontrado lanza 404 sin revelar nada", async () => {
    const promesa = conBooleano(false).exigirHabilitado("cotizacion.usa_link_publico", { comoNoEncontrado: true });

    await expect(promesa).rejects.toBeInstanceOf(NotFoundException);
  });

  it("si leer el parametro falla, las funciones operativas quedan HABILITADAS (permitir): un fallo de lectura no apaga el taller", async () => {
    await expect(
      conBooleano(new Error("BD caida")).exigirHabilitado("ot.usa_fotos"),
    ).resolves.toBeUndefined();
  });

  it("si leer falla en una funcion que bloquea (facturacion), se rechaza", async () => {
    await expect(
      conBooleano(new Error("BD caida")).exigirHabilitado("facturacion.usa_facturacion"),
    ).rejects.toBeInstanceOf(ForbiddenException);
  });
});
