$groups */ public function __construct( public Collection $groups, public bool $isResolvable = true, public bool $isUnrestricted = false, ) {} /** No existe ninguna restricción temporal configurada. */ public static function unrestricted(): self { return new self(collect(), isUnrestricted: true); } /** La configuración fuente está incompleta o es inconsistente. */ public static function unresolvable(): self { return new self(collect(), isResolvable: false); } /** Es válido cuando no tiene restricciones o algún grupo OR está activo. */ public function isValid(?CarbonInterface $at = null): bool { if (! $this->isResolvable) { return false; } return $this->isUnrestricted || $this->groups->contains( fn (ResolvedValidityGroup $group): bool => $group->isValid($at) ); } /** Sólo está vencido cuando todos los grupos OR ya vencieron. */ public function isExpired(?CarbonInterface $at = null): bool { return $this->isResolvable && ! $this->isUnrestricted && $this->groups->isNotEmpty() && $this->groups->every( fn (ResolvedValidityGroup $group): bool => $group->isExpired($at) ); } /** Inicio más temprano de todas las alternativas, usado como resumen. */ public function effectiveStartsAt(?CarbonInterface $at = null): ?CarbonInterface { return $this->groups ->map(fn (ResolvedValidityGroup $group): ?CarbonInterface => $group->effectiveStartsAt($at)) ->filter() ->sortBy(fn (CarbonInterface $startsAt): int => $startsAt->getTimestamp()) ->first(); } /** Vencimiento más tardío de todas las alternativas, usado como resumen. */ public function effectiveExpiresAt(?CarbonInterface $at = null): ?CarbonInterface { return $this->groups ->map(fn (ResolvedValidityGroup $group): ?CarbonInterface => $group->effectiveExpiresAt($at)) ->filter() ->sortByDesc(fn (CarbonInterface $expiresAt): int => $expiresAt->getTimestamp()) ->first(); } }