$tipo_dest, 'id_destinatario' => (string) $id_dest, 'tipo' => $tipo, 'titulo' => $titulo, 'mensaje' => $mensaje, 'url_accion' => $url, 'leida' => false, 'enviada_email' => false, 'creada_en' => now(), ]); // Intentar envío Push $this->enviarPush([['tipo' => $tipo_dest, 'id' => $id_dest]], $titulo, $mensaje, $url); return $notif; } /** * Enviar la misma notificación a múltiples destinatarios. * $destinatarios = [['tipo' => 'jugador', 'id' => 'XXX'], ...] */ public function enviarMasivo(array $destinatarios, string $tipo, string $titulo, string $mensaje, ?string $url = null): int { $rows = []; $ahora = now()->toDateTimeString(); foreach ($destinatarios as $dest) { $rows[] = [ 'tipo_destinatario' => $dest['tipo'], 'id_destinatario' => (string) $dest['id'], 'tipo' => $tipo, 'titulo' => $titulo, 'mensaje' => $mensaje, 'url_accion' => $url, 'leida' => false, 'enviada_email' => false, 'creada_en' => $ahora, ]; } if (empty($rows)) return 0; // Insert en chunks para no sobrecargar foreach (array_chunk($rows, 500) as $chunk) { Notificacion::insert($chunk); } // Intentar envío Push masivo $this->enviarPush($destinatarios, $titulo, $mensaje, $url); return count($rows); } /** * Lógica centralizada para enviar Web Push Notifications */ private function enviarPush(array $destinatarios, string $titulo, string $mensaje, ?string $url = null): void { $vPublic = env('VAPID_PUBLIC_KEY'); $vPrivate = env('VAPID_PRIVATE_KEY'); if (!$vPublic || !$vPrivate) return; // Buscar todas las suscripciones de estos usuarios $subsFound = PushSubscription::where(function($query) use ($destinatarios) { foreach ($destinatarios as $d) { $query->orWhere(function($q) use ($d) { $q->where('tipo_usuario', $d['tipo']) ->where('id_usuario', (string)$d['id']); }); } })->get(); if ($subsFound->isEmpty()) return; try { $auth = [ 'VAPID' => [ 'subject' => env('APP_URL', 'http://localhost'), 'publicKey' => $vPublic, 'privateKey' => $vPrivate, ], ]; $webPush = new WebPush($auth); $payload = json_encode([ 'title' => $titulo, 'body' => $mensaje, 'url' => $url ?: '/', ]); foreach ($subsFound as $sub) { $webPush->queueNotification( Subscription::create([ 'endpoint' => $sub->endpoint, 'publicKey' => $sub->p256dh, 'authToken' => $sub->auth, ]), $payload ); } foreach ($webPush->flush() as $report) { if (!$report->isSuccess()) { // Si falló (ej: suscripción expirada), la borramos para no reintentar if ($report->isSubscriptionExpired()) { $endpoint = $report->getEndpoint(); PushSubscription::where('endpoint', $endpoint)->delete(); } } } } catch (\Exception $e) { Log::error("Error enviando Web Push: " . $e->getMessage()); } } /** * Obtener notificaciones no leídas de un usuario. */ public function obtenerNoLeidas(string $tipo, string|int $id): Collection { return Notificacion::paraUsuario($tipo, $id) ->noLeidas() ->orderByDesc('creada_en') ->limit(50) ->get(); } /** * Obtener todas las notificaciones de un usuario (paginadas). */ public function obtenerTodas(string $tipo, string|int $id, int $perPage = 20) { return Notificacion::paraUsuario($tipo, $id) ->orderByDesc('creada_en') ->paginate($perPage); } /** * Contar notificaciones no leídas. */ public function contarNoLeidas(string $tipo, string|int $id): int { return Notificacion::paraUsuario($tipo, $id)->noLeidas()->count(); } /** * Marcar una notificación como leída (verificando pertenencia). */ public function marcarLeida(int $id_notif, string $tipo, string|int $id_dest): bool { return (bool) Notificacion::paraUsuario($tipo, $id_dest) ->where('id', $id_notif) ->update(['leida' => true]); } /** * Marcar todas como leídas para un usuario. */ public function marcarTodasLeidas(string $tipo, string|int $id_dest): int { return Notificacion::paraUsuario($tipo, $id_dest) ->noLeidas() ->update(['leida' => true]); } /** * Eliminar una notificación (verificando pertenencia). */ public function eliminar(int $id_notif, string $tipo, string|int $id_dest): bool { return (bool) Notificacion::paraUsuario($tipo, $id_dest) ->where('id', $id_notif) ->delete(); } /** * Eliminar todas las notificaciones de un usuario. */ public function eliminarTodas(string $tipo, string|int $id_dest): int { return Notificacion::paraUsuario($tipo, $id_dest)->delete(); } }