From dde4f014bb6fe474d8dab731bf0090a09d933503 Mon Sep 17 00:00:00 2001 From: Laucha1312 Date: Thu, 4 Jun 2026 14:49:28 -0300 Subject: [PATCH] Agrego archivos iniciales --- database/.gitignore | 1 + database/factories/UserFactory.php | 45 + .../0001_01_01_000000_create_users_table.php | 49 + .../0001_01_01_000001_create_cache_table.php | 35 + .../0001_01_01_000002_create_jobs_table.php | 59 + ..._22_000228_create_carousel_items_table.php | 71 + ...11653_add_id_club_to_admin_users_table.php | 41 + ...6_03_22_011702_create_categorias_table.php | 31 + .../2026_03_22_011710_create_pases_table.php | 37 + ..._023553_add_limite_qr_to_eventos_table.php | 22 + ...2_023601_add_qr_fields_to_clubes_table.php | 23 + ...23610_add_es_libre_to_categorias_table.php | 22 + ...22_044322_create_configuraciones_table.php | 30 + ...3_22_113000_increase_id_jugador_length.php | 74 + ...2026_03_22_114500_add_imagen_to_clubes.php | 32 + ...026_03_22_161609_create_sponsors_table.php | 32 + ...2026_03_24_062014_create_torneos_table.php | 30 + ...3_24_062015_create_torneo_equipo_table.php | 30 + ..._062016_add_id_torneo_to_eventos_table.php | 30 + ..._24_062511_add_scores_to_eventos_table.php | 29 + ..._24_062734_create_evento_jugador_table.php | 37 + ...65226_add_grupo_to_torneo_equipo_table.php | 28 + ...50_add_soft_deletes_to_critical_tables.php | 52 + ..._29_000001_create_notificaciones_table.php | 31 + ...000002_create_equipo_seguimiento_table.php | 27 + ..._29_040005_add_phases_to_eventos_table.php | 29 + ..._091800_fix_qrcodes_and_eventos_schema.php | 44 + ...112246_create_push_subscriptions_table.php | 34 + ...egoria_and_id_torneo_to_noticias_table.php | 46 + ...4_09_204613_create_agent_threads_table.php | 31 + ...00000_add_es_seleccion_to_clubes_table.php | 22 + ...d_timestamps_to_clubes_equipos_eventos.php | 44 + database/seeders/DatabaseSeeder.php | 25 + .../plans/2026-04-09-genius-agent.md | 1610 +++++++++++++++++ .../specs/2026-04-09-genius-agent-design.md | 244 +++ .../specs/2026-04-24-pagos-macro-design.md | 376 ++++ lang/es/validation.php | 173 ++ lang/vendor/backup/ar/notifications.php | 45 + lang/vendor/backup/bg/notifications.php | 45 + lang/vendor/backup/bn/notifications.php | 45 + lang/vendor/backup/cs/notifications.php | 45 + lang/vendor/backup/da/notifications.php | 45 + lang/vendor/backup/de/notifications.php | 45 + lang/vendor/backup/en/notifications.php | 45 + lang/vendor/backup/es/notifications.php | 45 + lang/vendor/backup/fa/notifications.php | 45 + lang/vendor/backup/fi/notifications.php | 45 + lang/vendor/backup/fr/notifications.php | 45 + lang/vendor/backup/he/notifications.php | 45 + lang/vendor/backup/hi/notifications.php | 45 + lang/vendor/backup/hr/notifications.php | 45 + lang/vendor/backup/id/notifications.php | 45 + lang/vendor/backup/it/notifications.php | 45 + lang/vendor/backup/ja/notifications.php | 45 + lang/vendor/backup/kk/notifications.php | 45 + lang/vendor/backup/ko/notifications.php | 45 + lang/vendor/backup/nl/notifications.php | 45 + lang/vendor/backup/no/notifications.php | 45 + lang/vendor/backup/pl/notifications.php | 45 + lang/vendor/backup/pt/notifications.php | 45 + lang/vendor/backup/pt_BR/notifications.php | 45 + lang/vendor/backup/ro/notifications.php | 45 + lang/vendor/backup/ru/notifications.php | 45 + lang/vendor/backup/sk/notifications.php | 45 + lang/vendor/backup/tr/notifications.php | 45 + lang/vendor/backup/uk/notifications.php | 45 + lang/vendor/backup/zh_CN/notifications.php | 45 + lang/vendor/backup/zh_TW/notifications.php | 45 + 68 files changed, 4971 insertions(+) create mode 100644 database/.gitignore create mode 100644 database/factories/UserFactory.php create mode 100644 database/migrations/0001_01_01_000000_create_users_table.php create mode 100644 database/migrations/0001_01_01_000001_create_cache_table.php create mode 100644 database/migrations/0001_01_01_000002_create_jobs_table.php create mode 100644 database/migrations/2026_03_22_000228_create_carousel_items_table.php create mode 100644 database/migrations/2026_03_22_011653_add_id_club_to_admin_users_table.php create mode 100644 database/migrations/2026_03_22_011702_create_categorias_table.php create mode 100644 database/migrations/2026_03_22_011710_create_pases_table.php create mode 100644 database/migrations/2026_03_22_023553_add_limite_qr_to_eventos_table.php create mode 100644 database/migrations/2026_03_22_023601_add_qr_fields_to_clubes_table.php create mode 100644 database/migrations/2026_03_22_023610_add_es_libre_to_categorias_table.php create mode 100644 database/migrations/2026_03_22_044322_create_configuraciones_table.php create mode 100644 database/migrations/2026_03_22_113000_increase_id_jugador_length.php create mode 100644 database/migrations/2026_03_22_114500_add_imagen_to_clubes.php create mode 100644 database/migrations/2026_03_22_161609_create_sponsors_table.php create mode 100644 database/migrations/2026_03_24_062014_create_torneos_table.php create mode 100644 database/migrations/2026_03_24_062015_create_torneo_equipo_table.php create mode 100644 database/migrations/2026_03_24_062016_add_id_torneo_to_eventos_table.php create mode 100644 database/migrations/2026_03_24_062511_add_scores_to_eventos_table.php create mode 100644 database/migrations/2026_03_24_062734_create_evento_jugador_table.php create mode 100644 database/migrations/2026_03_24_065226_add_grupo_to_torneo_equipo_table.php create mode 100644 database/migrations/2026_03_25_181950_add_soft_deletes_to_critical_tables.php create mode 100644 database/migrations/2026_03_29_000001_create_notificaciones_table.php create mode 100644 database/migrations/2026_03_29_000002_create_equipo_seguimiento_table.php create mode 100644 database/migrations/2026_03_29_040005_add_phases_to_eventos_table.php create mode 100644 database/migrations/2026_03_29_091800_fix_qrcodes_and_eventos_schema.php create mode 100644 database/migrations/2026_03_31_112246_create_push_subscriptions_table.php create mode 100644 database/migrations/2026_03_31_134500_add_categoria_and_id_torneo_to_noticias_table.php create mode 100644 database/migrations/2026_04_09_204613_create_agent_threads_table.php create mode 100644 database/migrations/2026_04_13_000000_add_es_seleccion_to_clubes_table.php create mode 100644 database/migrations/2026_05_16_120000_add_timestamps_to_clubes_equipos_eventos.php create mode 100644 database/seeders/DatabaseSeeder.php create mode 100644 docs/superpowers/plans/2026-04-09-genius-agent.md create mode 100644 docs/superpowers/specs/2026-04-09-genius-agent-design.md create mode 100644 docs/superpowers/specs/2026-04-24-pagos-macro-design.md create mode 100644 lang/es/validation.php create mode 100644 lang/vendor/backup/ar/notifications.php create mode 100644 lang/vendor/backup/bg/notifications.php create mode 100644 lang/vendor/backup/bn/notifications.php create mode 100644 lang/vendor/backup/cs/notifications.php create mode 100644 lang/vendor/backup/da/notifications.php create mode 100644 lang/vendor/backup/de/notifications.php create mode 100644 lang/vendor/backup/en/notifications.php create mode 100644 lang/vendor/backup/es/notifications.php create mode 100644 lang/vendor/backup/fa/notifications.php create mode 100644 lang/vendor/backup/fi/notifications.php create mode 100644 lang/vendor/backup/fr/notifications.php create mode 100644 lang/vendor/backup/he/notifications.php create mode 100644 lang/vendor/backup/hi/notifications.php create mode 100644 lang/vendor/backup/hr/notifications.php create mode 100644 lang/vendor/backup/id/notifications.php create mode 100644 lang/vendor/backup/it/notifications.php create mode 100644 lang/vendor/backup/ja/notifications.php create mode 100644 lang/vendor/backup/kk/notifications.php create mode 100644 lang/vendor/backup/ko/notifications.php create mode 100644 lang/vendor/backup/nl/notifications.php create mode 100644 lang/vendor/backup/no/notifications.php create mode 100644 lang/vendor/backup/pl/notifications.php create mode 100644 lang/vendor/backup/pt/notifications.php create mode 100644 lang/vendor/backup/pt_BR/notifications.php create mode 100644 lang/vendor/backup/ro/notifications.php create mode 100644 lang/vendor/backup/ru/notifications.php create mode 100644 lang/vendor/backup/sk/notifications.php create mode 100644 lang/vendor/backup/tr/notifications.php create mode 100644 lang/vendor/backup/uk/notifications.php create mode 100644 lang/vendor/backup/zh_CN/notifications.php create mode 100644 lang/vendor/backup/zh_TW/notifications.php diff --git a/database/.gitignore b/database/.gitignore new file mode 100644 index 0000000..9b19b93 --- /dev/null +++ b/database/.gitignore @@ -0,0 +1 @@ +*.sqlite* diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php new file mode 100644 index 0000000..c4ceb07 --- /dev/null +++ b/database/factories/UserFactory.php @@ -0,0 +1,45 @@ + + */ +class UserFactory extends Factory +{ + /** + * The current password being used by the factory. + */ + protected static ?string $password; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => fake()->name(), + 'email' => fake()->unique()->safeEmail(), + 'email_verified_at' => now(), + 'password' => static::$password ??= Hash::make('password'), + 'remember_token' => Str::random(10), + ]; + } + + /** + * Indicate that the model's email address should be unverified. + */ + public function unverified(): static + { + return $this->state(fn (array $attributes) => [ + 'email_verified_at' => null, + ]); + } +} diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php new file mode 100644 index 0000000..05fb5d9 --- /dev/null +++ b/database/migrations/0001_01_01_000000_create_users_table.php @@ -0,0 +1,49 @@ +id(); + $table->string('name'); + $table->string('email')->unique(); + $table->timestamp('email_verified_at')->nullable(); + $table->string('password'); + $table->rememberToken(); + $table->timestamps(); + }); + + Schema::create('password_reset_tokens', function (Blueprint $table) { + $table->string('email')->primary(); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + }); + + Schema::create('sessions', function (Blueprint $table) { + $table->string('id')->primary(); + $table->foreignId('user_id')->nullable()->index(); + $table->string('ip_address', 45)->nullable(); + $table->text('user_agent')->nullable(); + $table->longText('payload'); + $table->integer('last_activity')->index(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('users'); + Schema::dropIfExists('password_reset_tokens'); + Schema::dropIfExists('sessions'); + } +}; diff --git a/database/migrations/0001_01_01_000001_create_cache_table.php b/database/migrations/0001_01_01_000001_create_cache_table.php new file mode 100644 index 0000000..ed758bd --- /dev/null +++ b/database/migrations/0001_01_01_000001_create_cache_table.php @@ -0,0 +1,35 @@ +string('key')->primary(); + $table->mediumText('value'); + $table->integer('expiration')->index(); + }); + + Schema::create('cache_locks', function (Blueprint $table) { + $table->string('key')->primary(); + $table->string('owner'); + $table->integer('expiration')->index(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('cache'); + Schema::dropIfExists('cache_locks'); + } +}; diff --git a/database/migrations/0001_01_01_000002_create_jobs_table.php b/database/migrations/0001_01_01_000002_create_jobs_table.php new file mode 100644 index 0000000..967fbf5 --- /dev/null +++ b/database/migrations/0001_01_01_000002_create_jobs_table.php @@ -0,0 +1,59 @@ +id(); + $table->string('queue'); + $table->longText('payload'); + $table->unsignedTinyInteger('attempts'); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + + $table->index(['queue', 'reserved_at', 'available_at']); + }); + + Schema::create('job_batches', function (Blueprint $table) { + $table->string('id')->primary(); + $table->string('name'); + $table->integer('total_jobs'); + $table->integer('pending_jobs'); + $table->integer('failed_jobs'); + $table->longText('failed_job_ids'); + $table->mediumText('options')->nullable(); + $table->integer('cancelled_at')->nullable(); + $table->integer('created_at'); + $table->integer('finished_at')->nullable(); + }); + + Schema::create('failed_jobs', function (Blueprint $table) { + $table->id(); + $table->string('uuid')->unique(); + $table->text('connection'); + $table->text('queue'); + $table->longText('payload'); + $table->longText('exception'); + $table->timestamp('failed_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('jobs'); + Schema::dropIfExists('job_batches'); + Schema::dropIfExists('failed_jobs'); + } +}; diff --git a/database/migrations/2026_03_22_000228_create_carousel_items_table.php b/database/migrations/2026_03_22_000228_create_carousel_items_table.php new file mode 100644 index 0000000..7f3b235 --- /dev/null +++ b/database/migrations/2026_03_22_000228_create_carousel_items_table.php @@ -0,0 +1,71 @@ +id(); + $table->string('titulo')->nullable(); + $table->string('subtitulo')->nullable(); + $table->string('boton_texto')->nullable(); + $table->string('boton_enlace')->nullable(); + $table->string('imagen'); + $table->integer('orden')->default(0); + $table->boolean('activo')->default(true); + $table->timestamps(); + }); + + // Insert default items to preserve the first 3 original items + DB::table('carousel_items')->insert([ + [ + 'titulo' => 'Bienvenido a OnAPB', + 'subtitulo' => 'La nueva forma de vivir el básquet en Paraná', + 'boton_texto' => 'Ver partidos', + 'boton_enlace' => '/eventos', + 'imagen' => 'hero1.jpeg', + 'orden' => 1, + 'activo' => true, + 'created_at' => now(), + 'updated_at' => now(), + ], + [ + 'titulo' => 'El basquet en tus manos', + 'subtitulo' => 'Seguilo como nunca antes', + 'boton_texto' => 'Unite', + 'boton_enlace' => '/asociate', + 'imagen' => 'hero2.jpg', + 'orden' => 2, + 'activo' => true, + 'created_at' => now(), + 'updated_at' => now(), + ], + [ + 'titulo' => 'Sumate a la comunidad', + 'subtitulo' => 'Asociate y disfrutá beneficios exclusivos', + 'boton_texto' => 'Lugares', + 'boton_enlace' => '/promos', + 'imagen' => 'hero3.jpg', + 'orden' => 3, + 'activo' => true, + 'created_at' => now(), + 'updated_at' => now(), + ] + ]); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('carousel_items'); + } +}; diff --git a/database/migrations/2026_03_22_011653_add_id_club_to_admin_users_table.php b/database/migrations/2026_03_22_011653_add_id_club_to_admin_users_table.php new file mode 100644 index 0000000..64671fd --- /dev/null +++ b/database/migrations/2026_03_22_011653_add_id_club_to_admin_users_table.php @@ -0,0 +1,41 @@ +integer('id_club')->nullable()->after('password'); + } else { + // Si la columna ya se creó en error en un intento anterior pero mal tipada + $table->integer('id_club')->nullable()->change(); + } + // Agregamos la constrain después de asegurarnos de su tipo + }); + + Schema::table('admin_users', function (Blueprint $table) { + // Intentar dropear llave si existe para evitar error por si acaso no va, + // pero como falló en el fk, mejor tratamos de crearla: + $table->foreign('id_club')->references('id_club')->on('clubes')->onDelete('set null'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('admin_users', function (Blueprint $table) { + $table->dropForeign(['id_club']); + $table->dropColumn('id_club'); + }); + } +}; diff --git a/database/migrations/2026_03_22_011702_create_categorias_table.php b/database/migrations/2026_03_22_011702_create_categorias_table.php new file mode 100644 index 0000000..b98de9c --- /dev/null +++ b/database/migrations/2026_03_22_011702_create_categorias_table.php @@ -0,0 +1,31 @@ +id('id_categoria'); + $table->string('nombre'); + $table->integer('edad_min'); + $table->integer('edad_max'); + $table->string('genero')->nullable(); // M, F, Mixto... + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('categorias'); + } +}; diff --git a/database/migrations/2026_03_22_011710_create_pases_table.php b/database/migrations/2026_03_22_011710_create_pases_table.php new file mode 100644 index 0000000..8061a1d --- /dev/null +++ b/database/migrations/2026_03_22_011710_create_pases_table.php @@ -0,0 +1,37 @@ +id('id_pase'); + $table->char('id_jugador', 36)->nullable(); + $table->integer('id_club_origen')->nullable(); + $table->integer('id_club_destino')->nullable(); + $table->string('estado')->default('Pendiente'); // Pendiente, Aprobado, Rechazado + + // Si la FK en jugadores es de tipo uuid, usamos char(36) + $table->foreign('id_jugador')->references('id_jugador')->on('jugadores')->onDelete('cascade'); + $table->foreign('id_club_origen')->references('id_club')->on('clubes')->onDelete('cascade'); + $table->foreign('id_club_destino')->references('id_club')->on('clubes')->onDelete('cascade'); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('pases'); + } +}; diff --git a/database/migrations/2026_03_22_023553_add_limite_qr_to_eventos_table.php b/database/migrations/2026_03_22_023553_add_limite_qr_to_eventos_table.php new file mode 100644 index 0000000..b7b7e03 --- /dev/null +++ b/database/migrations/2026_03_22_023553_add_limite_qr_to_eventos_table.php @@ -0,0 +1,22 @@ +integer('limite_qr_jugador')->default(3)->after('precio'); + }); + } + + public function down(): void + { + Schema::table('eventos', function (Blueprint $table) { + $table->dropColumn('limite_qr_jugador'); + }); + } +}; diff --git a/database/migrations/2026_03_22_023601_add_qr_fields_to_clubes_table.php b/database/migrations/2026_03_22_023601_add_qr_fields_to_clubes_table.php new file mode 100644 index 0000000..a4ebdf5 --- /dev/null +++ b/database/migrations/2026_03_22_023601_add_qr_fields_to_clubes_table.php @@ -0,0 +1,23 @@ +string('qr_background')->nullable(); + $table->string('qr_color_texto')->default('#000000'); + }); + } + + public function down(): void + { + Schema::table('clubes', function (Blueprint $table) { + $table->dropColumn(['qr_background', 'qr_color_texto']); + }); + } +}; diff --git a/database/migrations/2026_03_22_023610_add_es_libre_to_categorias_table.php b/database/migrations/2026_03_22_023610_add_es_libre_to_categorias_table.php new file mode 100644 index 0000000..4ba7750 --- /dev/null +++ b/database/migrations/2026_03_22_023610_add_es_libre_to_categorias_table.php @@ -0,0 +1,22 @@ +boolean('es_libre')->default(false); + }); + } + + public function down(): void + { + Schema::table('categorias', function (Blueprint $table) { + $table->dropColumn('es_libre'); + }); + } +}; diff --git a/database/migrations/2026_03_22_044322_create_configuraciones_table.php b/database/migrations/2026_03_22_044322_create_configuraciones_table.php new file mode 100644 index 0000000..d5bc4cd --- /dev/null +++ b/database/migrations/2026_03_22_044322_create_configuraciones_table.php @@ -0,0 +1,30 @@ +id(); + $table->string('clave')->unique(); + $table->text('valor')->nullable(); + $table->string('descripcion')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('configuraciones'); + } +}; diff --git a/database/migrations/2026_03_22_113000_increase_id_jugador_length.php b/database/migrations/2026_03_22_113000_increase_id_jugador_length.php new file mode 100644 index 0000000..486f208 --- /dev/null +++ b/database/migrations/2026_03_22_113000_increase_id_jugador_length.php @@ -0,0 +1,74 @@ +dropForeign('jugador_equipo_id_jugador_foreign'); + }); + } catch (\Exception $e) {} + + try { + // Drop confirmed name + DB::statement('ALTER TABLE jugador_equipo DROP FOREIGN KEY jugador_equipo_ibfk_1'); + } catch (\Exception $e) {} + + try { + DB::statement('ALTER TABLE qr_codes DROP FOREIGN KEY qr_codes_ibfk_2'); + } catch (\Exception $e) {} + + // 2. Increase the length of id_jugador in the main table + Schema::table('jugadores', function (Blueprint $table) { + $table->string('id_jugador', 20)->change(); + }); + + // 3. Increase the length in referencing tables + Schema::table('pases', function (Blueprint $table) { + $table->string('id_jugador', 20)->change(); + }); + + Schema::table('jugador_equipo', function (Blueprint $table) { + $table->string('id_jugador', 20)->change(); + }); + + Schema::table('qr_codes', function (Blueprint $table) { + $table->string('id_jugador', 20)->nullable()->change(); + }); + + // 4. Restore foreign keys (Laravel will pick names) + Schema::table('pases', function (Blueprint $table) { + // Check if column exists and is not already a FK + try { + $table->foreign('id_jugador')->references('id_jugador')->on('jugadores')->onDelete('cascade'); + } catch (\Exception $e) {} + }); + + Schema::table('jugador_equipo', function (Blueprint $table) { + $table->foreign('id_jugador')->references('id_jugador')->on('jugadores')->onDelete('cascade'); + }); + + Schema::table('qr_codes', function (Blueprint $table) { + $table->foreign('id_jugador')->references('id_jugador')->on('jugadores')->onDelete('set null'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // No revert needed for length increase + } +}; diff --git a/database/migrations/2026_03_22_114500_add_imagen_to_clubes.php b/database/migrations/2026_03_22_114500_add_imagen_to_clubes.php new file mode 100644 index 0000000..106c8ab --- /dev/null +++ b/database/migrations/2026_03_22_114500_add_imagen_to_clubes.php @@ -0,0 +1,32 @@ +string('imagen')->nullable()->after('nombre'); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('clubes', function (Blueprint $table) { + if (Schema::hasColumn('clubes', 'imagen')) { + $table->dropColumn('imagen'); + } + }); + } +}; diff --git a/database/migrations/2026_03_22_161609_create_sponsors_table.php b/database/migrations/2026_03_22_161609_create_sponsors_table.php new file mode 100644 index 0000000..b2b5d70 --- /dev/null +++ b/database/migrations/2026_03_22_161609_create_sponsors_table.php @@ -0,0 +1,32 @@ +id('id_sponsor'); + $blueprint->string('nombre', 100); + $blueprint->string('imagen', 255); + $blueprint->string('url', 255)->nullable(); + $blueprint->boolean('activo')->default(true); + $blueprint->integer('orden')->default(0); + $blueprint->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('sponsors'); + } +}; diff --git a/database/migrations/2026_03_24_062014_create_torneos_table.php b/database/migrations/2026_03_24_062014_create_torneos_table.php new file mode 100644 index 0000000..166366a --- /dev/null +++ b/database/migrations/2026_03_24_062014_create_torneos_table.php @@ -0,0 +1,30 @@ +id(); + $table->string('nombre'); + $table->date('fecha_inicio')->nullable(); + $table->date('fecha_fin')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('torneos'); + } +}; diff --git a/database/migrations/2026_03_24_062015_create_torneo_equipo_table.php b/database/migrations/2026_03_24_062015_create_torneo_equipo_table.php new file mode 100644 index 0000000..45b295f --- /dev/null +++ b/database/migrations/2026_03_24_062015_create_torneo_equipo_table.php @@ -0,0 +1,30 @@ +id(); + $table->foreignId('id_torneo')->constrained('torneos')->onDelete('cascade'); + $table->unsignedBigInteger('id_equipo'); + $table->foreign('id_equipo')->references('id_equipo')->on('equipos')->onDelete('cascade'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('torneo_equipo'); + } +}; diff --git a/database/migrations/2026_03_24_062016_add_id_torneo_to_eventos_table.php b/database/migrations/2026_03_24_062016_add_id_torneo_to_eventos_table.php new file mode 100644 index 0000000..bc2d7bd --- /dev/null +++ b/database/migrations/2026_03_24_062016_add_id_torneo_to_eventos_table.php @@ -0,0 +1,30 @@ +unsignedBigInteger('id_torneo')->nullable()->after('id_evento'); + $table->foreign('id_torneo')->references('id')->on('torneos')->onDelete('set null'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('eventos', function (Blueprint $table) { + $table->dropForeign(['id_torneo']); + $table->dropColumn('id_torneo'); + }); + } +}; diff --git a/database/migrations/2026_03_24_062511_add_scores_to_eventos_table.php b/database/migrations/2026_03_24_062511_add_scores_to_eventos_table.php new file mode 100644 index 0000000..8a8e89f --- /dev/null +++ b/database/migrations/2026_03_24_062511_add_scores_to_eventos_table.php @@ -0,0 +1,29 @@ +integer('marcador_local')->nullable()->default(0)->after('id_equipo_visitante'); + $col->integer('marcador_visitante')->nullable()->default(0)->after('marcador_local'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('eventos', function (Blueprint $col) { + $col->dropColumn(['marcador_local', 'marcador_visitante']); + }); + } +}; diff --git a/database/migrations/2026_03_24_062734_create_evento_jugador_table.php b/database/migrations/2026_03_24_062734_create_evento_jugador_table.php new file mode 100644 index 0000000..86cae92 --- /dev/null +++ b/database/migrations/2026_03_24_062734_create_evento_jugador_table.php @@ -0,0 +1,37 @@ +id(); + $table->string('id_evento'); + $table->string('id_jugador'); + $table->integer('puntos')->default(0); + $table->integer('faltas')->default(0); + $table->timestamps(); + + $table->foreign('id_evento')->references('id_evento')->on('eventos')->onDelete('cascade'); + $table->foreign('id_jugador')->references('id_jugador')->on('jugadores')->onDelete('cascade'); + + // Un jugador solo puede tener un registro de puntos por evento + $table->unique(['id_evento', 'id_jugador']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('evento_jugador'); + } +}; diff --git a/database/migrations/2026_03_24_065226_add_grupo_to_torneo_equipo_table.php b/database/migrations/2026_03_24_065226_add_grupo_to_torneo_equipo_table.php new file mode 100644 index 0000000..3caaefe --- /dev/null +++ b/database/migrations/2026_03_24_065226_add_grupo_to_torneo_equipo_table.php @@ -0,0 +1,28 @@ +string('grupo')->nullable()->after('id_equipo'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('torneo_equipo', function (Blueprint $table) { + $table->dropColumn('grupo'); + }); + } +}; diff --git a/database/migrations/2026_03_25_181950_add_soft_deletes_to_critical_tables.php b/database/migrations/2026_03_25_181950_add_soft_deletes_to_critical_tables.php new file mode 100644 index 0000000..51e9cd6 --- /dev/null +++ b/database/migrations/2026_03_25_181950_add_soft_deletes_to_critical_tables.php @@ -0,0 +1,52 @@ +softDeletes(); + }); + Schema::table('clubes', function (Blueprint $table) { + $table->softDeletes(); + }); + Schema::table('equipos', function (Blueprint $table) { + $table->softDeletes(); + }); + Schema::table('eventos', function (Blueprint $table) { + $table->softDeletes(); + }); + Schema::table('torneos', function (Blueprint $table) { + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('jugadores', function (Blueprint $table) { + $table->dropSoftDeletes(); + }); + Schema::table('clubes', function (Blueprint $table) { + $table->dropSoftDeletes(); + }); + Schema::table('equipos', function (Blueprint $table) { + $table->dropSoftDeletes(); + }); + Schema::table('eventos', function (Blueprint $table) { + $table->dropSoftDeletes(); + }); + Schema::table('torneos', function (Blueprint $table) { + $table->dropSoftDeletes(); + }); + } +}; diff --git a/database/migrations/2026_03_29_000001_create_notificaciones_table.php b/database/migrations/2026_03_29_000001_create_notificaciones_table.php new file mode 100644 index 0000000..e2f3c17 --- /dev/null +++ b/database/migrations/2026_03_29_000001_create_notificaciones_table.php @@ -0,0 +1,31 @@ +id(); + $table->enum('tipo_destinatario', ['jugador', 'aficionado'])->index(); + $table->string('id_destinatario'); // jugador.id_jugador o aficionado.id_aficionado + $table->enum('tipo', ['partido', 'resultado', 'sistema', 'seguimiento'])->default('sistema'); + $table->string('titulo'); + $table->text('mensaje'); + $table->string('url_accion')->nullable(); + $table->boolean('leida')->default(false); + $table->boolean('enviada_email')->default(false); + $table->timestamp('creada_en')->useCurrent(); + + $table->index(['tipo_destinatario', 'id_destinatario', 'leida']); + }); + } + + public function down(): void + { + Schema::dropIfExists('notificaciones'); + } +}; diff --git a/database/migrations/2026_03_29_000002_create_equipo_seguimiento_table.php b/database/migrations/2026_03_29_000002_create_equipo_seguimiento_table.php new file mode 100644 index 0000000..5e49ea8 --- /dev/null +++ b/database/migrations/2026_03_29_000002_create_equipo_seguimiento_table.php @@ -0,0 +1,27 @@ +id(); + $table->unsignedInteger('id_equipo'); + $table->enum('tipo_usuario', ['jugador', 'aficionado']); + $table->string('id_usuario'); // id_jugador (string) o id_aficionado (int as string) + $table->timestamp('created_at')->useCurrent(); + + $table->unique(['id_equipo', 'tipo_usuario', 'id_usuario'], 'uq_seguimiento'); + $table->index(['tipo_usuario', 'id_usuario']); + }); + } + + public function down(): void + { + Schema::dropIfExists('equipo_seguimiento'); + } +}; diff --git a/database/migrations/2026_03_29_040005_add_phases_to_eventos_table.php b/database/migrations/2026_03_29_040005_add_phases_to_eventos_table.php new file mode 100644 index 0000000..2aa3e4e --- /dev/null +++ b/database/migrations/2026_03_29_040005_add_phases_to_eventos_table.php @@ -0,0 +1,29 @@ +integer('fase')->default(0)->after('id_torneo')->comment('0=Regular, 1=Cuartos, 2=Semis, 3=Final'); + $table->integer('numero_partido_bracket')->nullable()->after('fase'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('eventos', function (Blueprint $table) { + $table->dropColumn(['fase', 'numero_partido_bracket']); + }); + } +}; diff --git a/database/migrations/2026_03_29_091800_fix_qrcodes_and_eventos_schema.php b/database/migrations/2026_03_29_091800_fix_qrcodes_and_eventos_schema.php new file mode 100644 index 0000000..3738648 --- /dev/null +++ b/database/migrations/2026_03_29_091800_fix_qrcodes_and_eventos_schema.php @@ -0,0 +1,44 @@ + VARCHAR) + // Usamos DB::statement por compatibilidad con MariaDB/MySQL sin necesidad de dbal + DB::statement("ALTER TABLE qr_codes MODIFY tipo_qr VARCHAR(50) NOT NULL"); + + // 2. Corregir marcadores en eventos (0 -> NULL) + DB::statement("ALTER TABLE eventos MODIFY marcador_local INT NULL DEFAULT NULL"); + DB::statement("ALTER TABLE eventos MODIFY marcador_visitante INT NULL DEFAULT NULL"); + + // 3. Limpieza de datos: partidos con 0-0 que deberían ser Pendientes (NULL) + // Solo afectamos registros donde ambos son 0 para no romper resultados reales (aunque raros en básquet) + DB::table('eventos') + ->where('marcador_local', 0) + ->where('marcador_visitante', 0) + ->update([ + 'marcador_local' => null, + 'marcador_visitante' => null + ]); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // Volver a ENUM y Default 0 + DB::statement("ALTER TABLE qr_codes MODIFY tipo_qr ENUM('invitado', 'publico') NOT NULL"); + DB::statement("ALTER TABLE eventos MODIFY marcador_local INT NOT NULL DEFAULT 0"); + DB::statement("ALTER TABLE eventos MODIFY marcador_visitante INT NOT NULL DEFAULT 0"); + } +}; diff --git a/database/migrations/2026_03_31_112246_create_push_subscriptions_table.php b/database/migrations/2026_03_31_112246_create_push_subscriptions_table.php new file mode 100644 index 0000000..e892afb --- /dev/null +++ b/database/migrations/2026_03_31_112246_create_push_subscriptions_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('id_usuario'); // ID del jugador o aficionado + $table->string('tipo_usuario'); // 'jugador' o 'aficionado' + $table->text('endpoint'); // URL de suscripción del navegador + $table->string('p256dh'); // Clave pública del navegador + $table->string('auth'); // Token de autenticación del navegador + $table->timestamps(); + + $table->index(['id_usuario', 'tipo_usuario']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('push_subscriptions'); + } +}; diff --git a/database/migrations/2026_03_31_134500_add_categoria_and_id_torneo_to_noticias_table.php b/database/migrations/2026_03_31_134500_add_categoria_and_id_torneo_to_noticias_table.php new file mode 100644 index 0000000..beb8ec0 --- /dev/null +++ b/database/migrations/2026_03_31_134500_add_categoria_and_id_torneo_to_noticias_table.php @@ -0,0 +1,46 @@ +string('categoria', 50)->nullable()->after('contenido'); + } + + // Añadimos id_torneo si no existe + if (!Schema::hasColumn('noticias', 'id_torneo')) { + $table->unsignedBigInteger('id_torneo')->nullable()->after('categoria'); + // Intentamos añadir la clave foránea solo si la tabla torneos existe (por seguridad) + if (Schema::hasTable('torneos')) { + $table->foreign('id_torneo')->references('id')->on('torneos')->onDelete('set null'); + } + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('noticias', function (Blueprint $table) { + if (Schema::hasColumn('noticias', 'id_torneo')) { + $table->dropForeign(['id_torneo']); + $table->dropColumn('id_torneo'); + } + if (Schema::hasColumn('noticias', 'categoria')) { + $table->dropColumn('categoria'); + } + }); + } +}; diff --git a/database/migrations/2026_04_09_204613_create_agent_threads_table.php b/database/migrations/2026_04_09_204613_create_agent_threads_table.php new file mode 100644 index 0000000..008586e --- /dev/null +++ b/database/migrations/2026_04_09_204613_create_agent_threads_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('thread_id', 36)->unique(); + $table->integer('admin_id'); + $table->json('messages'); + $table->timestamps(); + $table->timestamp('expires_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('agent_threads'); + } +}; diff --git a/database/migrations/2026_04_13_000000_add_es_seleccion_to_clubes_table.php b/database/migrations/2026_04_13_000000_add_es_seleccion_to_clubes_table.php new file mode 100644 index 0000000..41cf7ab --- /dev/null +++ b/database/migrations/2026_04_13_000000_add_es_seleccion_to_clubes_table.php @@ -0,0 +1,22 @@ +boolean('es_seleccion')->default(false)->after('nombre'); + }); + } + + public function down(): void + { + Schema::table('clubes', function (Blueprint $table) { + $table->dropColumn('es_seleccion'); + }); + } +}; diff --git a/database/migrations/2026_05_16_120000_add_timestamps_to_clubes_equipos_eventos.php b/database/migrations/2026_05_16_120000_add_timestamps_to_clubes_equipos_eventos.php new file mode 100644 index 0000000..6298886 --- /dev/null +++ b/database/migrations/2026_05_16_120000_add_timestamps_to_clubes_equipos_eventos.php @@ -0,0 +1,44 @@ +getTable(), 'created_at')) { + $table->timestamp('created_at')->nullable(); + } + if (!Schema::hasColumn($table->getTable(), 'updated_at')) { + $table->timestamp('updated_at')->nullable(); + } + }); + } + } + + public function down(): void + { + foreach (['clubes', 'equipos', 'eventos'] as $tabla) { + Schema::table($tabla, function (Blueprint $table) { + if (Schema::hasColumn($table->getTable(), 'updated_at')) { + $table->dropColumn('updated_at'); + } + if (Schema::hasColumn($table->getTable(), 'created_at')) { + $table->dropColumn('created_at'); + } + }); + } + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php new file mode 100644 index 0000000..6b901f8 --- /dev/null +++ b/database/seeders/DatabaseSeeder.php @@ -0,0 +1,25 @@ +create(); + + User::factory()->create([ + 'name' => 'Test User', + 'email' => 'test@example.com', + ]); + } +} diff --git a/docs/superpowers/plans/2026-04-09-genius-agent.md b/docs/superpowers/plans/2026-04-09-genius-agent.md new file mode 100644 index 0000000..78eb978 --- /dev/null +++ b/docs/superpowers/plans/2026-04-09-genius-agent.md @@ -0,0 +1,1610 @@ +# OnAPB Genius Agent — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Agregar un agente de IA conversacional (Prism PHP + Gemini 1.5 Flash) con tools de function-calling para admins y asistente de navegación para usuarios públicos. + +**Architecture:** `GeniusAgentService` orquesta las llamadas a Prism con lista de tools dinámica (vacía para público, 5 tools para admin). Conversaciones de admin persisten en MySQL `agent_threads`; conversaciones públicas viven en sesión PHP. Sin streaming — respuesta JSON completa para compatibilidad con Hostinger. + +**Tech Stack:** PHP 8.2, Laravel 12, `prism-php/prism`, Google Gemini 1.5 Flash, MySQL, Bootstrap 5, vanilla JS. + +--- + +## Mapa de Archivos + +### Creados +| Archivo | Responsabilidad | +|---|---| +| `app/AI/Tools/ListarEquiposTool.php` | Invokable: consulta equipos con filtro torneo/grupo | +| `app/AI/Tools/ListarEventosTool.php` | Invokable: consulta partidos con filtro fecha/torneo | +| `app/AI/Tools/CrearPartidoTool.php` | Invokable: inserta un Evento en BD | +| `app/AI/Tools/CargarPuntajeTool.php` | Invokable: actualiza marcadores de un Evento | +| `app/AI/Tools/RedactarNoticiaTool.php` | Invokable: inserta una Noticia en BD | +| `app/AI/Prompts/SystemPromptAdmin.php` | Retorna string del system prompt para admins | +| `app/AI/Prompts/SystemPromptPublic.php` | Retorna string del system prompt público + manual RAG | +| `app/Services/GeniusAgentService.php` | Orquesta Prism: selecciona modo, ejecuta, persiste historial | +| `app/Http/Controllers/GeniusAgentController.php` | Recibe POST /agent/chat, valida, llama service, retorna JSON | +| `app/Models/AgentThread.php` | Eloquent model para historial admin | +| `app/Console/Commands/PurgeAgentThreads.php` | Artisan command: elimina threads expirados | +| `database/migrations/xxxx_create_agent_threads_table.php` | Tabla agent_threads | +| `resources/views/components/genius-chat.blade.php` | Widget chat bubble (Bootstrap + vanilla JS) | + +### Modificados +| Archivo | Cambio | +|---|---| +| `.env` / `.env.example` | Agregar `GEMINI_API_KEY` | +| `routes/web.php` | `POST /agent/chat` con throttle | +| `routes/console.php` | Schedule daily purge | +| `resources/views/layouts/app.blade.php` | Include del componente chat | + +### Tests +| Archivo | Qué prueba | +|---|---| +| `tests/Unit/AI/Tools/ListarEquiposToolTest.php` | Filtrado por torneo y grupo | +| `tests/Unit/AI/Tools/ListarEventosToolTest.php` | Filtrado por fecha e id_torneo | +| `tests/Unit/AI/Tools/CrearPartidoToolTest.php` | Inserta Evento correctamente | +| `tests/Unit/AI/Tools/CargarPuntajeToolTest.php` | Actualiza marcadores; falla gracefully si no existe | +| `tests/Unit/AI/Tools/RedactarNoticiaToolTest.php` | Inserta Noticia correctamente | +| `tests/Feature/GeniusAgentControllerTest.php` | HTTP layer: validación, throttle, error handling | + +--- + +## Tarea 1: Instalar Prism PHP y configurar Gemini + +**Archivos:** +- Modify: `composer.json` (via composer require) +- Modify: `.env` +- Modify: `.env.example` + +- [ ] **Paso 1: Instalar el paquete** + +```bash +composer require prism-php/prism +``` + +Resultado esperado: `prism-php/prism` aparece en `composer.json` bajo `require`. + +- [ ] **Paso 2: Publicar config de Prism** + +```bash +php artisan vendor:publish --provider="EchoLabs\Prism\PrismServiceProvider" --tag="prism-config" +``` + +Resultado esperado: archivo `config/prism.php` creado. + +- [ ] **Paso 3: Agregar API key en `.env`** + +Agregar al final de `.env`: +```env +GEMINI_API_KEY=tu_api_key_de_google_ai_studio +``` + +Agregar al final de `.env.example`: +```env +GEMINI_API_KEY= +``` + +- [ ] **Paso 4: Verificar que config/prism.php tiene la sección de Gemini** + +Abrir `config/prism.php` y confirmar que existe algo similar a: +```php +'gemini' => [ + 'api_key' => env('GEMINI_API_KEY', ''), +], +``` + +Si no existe, agregar ese bloque dentro de `'providers'`. + +- [ ] **Paso 5: Limpiar config cache** + +```bash +php artisan config:clear +``` + +- [ ] **Paso 6: Commit** + +```bash +git add composer.json composer.lock config/prism.php .env.example +git commit -m "chore: install prism-php/prism and configure Gemini provider" +``` + +--- + +## Tarea 2: Migration y Modelo AgentThread + +**Archivos:** +- Create: `database/migrations/xxxx_create_agent_threads_table.php` +- Create: `app/Models/AgentThread.php` + +- [ ] **Paso 1: Crear la migration** + +```bash +php artisan make:migration create_agent_threads_table +``` + +Abrir el archivo generado y reemplazar el contenido del método `up()`: + +```php +public function up(): void +{ + Schema::create('agent_threads', function (Blueprint $table) { + $table->id(); + $table->string('thread_id', 36)->unique(); + $table->integer('admin_id'); + $table->json('messages'); + $table->timestamps(); + $table->timestamp('expires_at'); + }); +} + +public function down(): void +{ + Schema::dropIfExists('agent_threads'); +} +``` + +- [ ] **Paso 2: Crear el modelo `app/Models/AgentThread.php`** + +```php + 'array', + 'expires_at' => 'datetime', + ]; + + public static function findOrCreateForAdmin(?string $threadId, int $adminId): static + { + if ($threadId) { + $thread = static::where('thread_id', $threadId) + ->where('admin_id', $adminId) + ->first(); + if ($thread) { + return $thread; + } + } + + return static::create([ + 'thread_id' => (string) Str::uuid(), + 'admin_id' => $adminId, + 'messages' => [], + 'expires_at' => now()->addDays(30), + ]); + } +} +``` + +- [ ] **Paso 3: Ejecutar la migration** + +```bash +php artisan migrate +``` + +Resultado esperado: `Migrating: xxxx_create_agent_threads_table` → `Migrated`. + +- [ ] **Paso 4: Verificar el modelo con tinker** + +```bash +php artisan tinker --execute="App\Models\AgentThread::create(['thread_id'=>'test-uuid','admin_id'=>1,'messages'=>[],'expires_at'=>now()->addDays(30)]); echo App\Models\AgentThread::count();" +``` + +Resultado esperado: `1` + +- [ ] **Paso 5: Commit** + +```bash +git add database/migrations/ app/Models/AgentThread.php +git commit -m "feat: add agent_threads migration and AgentThread model" +``` + +--- + +## Tarea 3: Tools de solo lectura — ListarEquipos y ListarEventos + +**Archivos:** +- Create: `app/AI/Tools/ListarEquiposTool.php` +- Create: `app/AI/Tools/ListarEventosTool.php` +- Create: `tests/Unit/AI/Tools/ListarEquiposToolTest.php` +- Create: `tests/Unit/AI/Tools/ListarEventosToolTest.php` + +- [ ] **Paso 1: Crear directorios** + +```bash +mkdir -p app/AI/Tools app/AI/Prompts +mkdir -p tests/Unit/AI/Tools +``` + +- [ ] **Paso 2: Escribir el test de ListarEquiposTool** + +Crear `tests/Unit/AI/Tools/ListarEquiposToolTest.php`: + +```php + 1, 'nombre' => 'Club Test']); + Equipo::create(['id_club' => 1, 'categoria' => 'Primera', 'division' => 'A']); + + $tool = new ListarEquiposTool(); + $result = json_decode($tool(), true); + + $this->assertCount(1, $result); + $this->assertEquals('Primera', $result[0]['categoria']); + } + + public function test_filtra_por_torneo_y_grupo(): void + { + Club::create(['id_club' => 1, 'nombre' => 'Club A']); + Club::create(['id_club' => 2, 'nombre' => 'Club B']); + $eq1 = Equipo::create(['id_club' => 1, 'categoria' => 'Primera', 'division' => 'A']); + $eq2 = Equipo::create(['id_club' => 2, 'categoria' => 'Primera', 'division' => 'A']); + $torneo = Torneo::create(['nombre' => 'Torneo 2025', 'fecha_inicio' => now(), 'fecha_fin' => now()->addMonths(3)]); + + \DB::table('torneo_equipo')->insert([ + ['id_torneo' => $torneo->id, 'id_equipo' => $eq1->id_equipo, 'grupo' => 'A'], + ['id_torneo' => $torneo->id, 'id_equipo' => $eq2->id_equipo, 'grupo' => 'B'], + ]); + + $tool = new ListarEquiposTool(); + $result = json_decode($tool(id_torneo: $torneo->id, grupo: 'A'), true); + + $this->assertCount(1, $result); + $this->assertEquals($eq1->id_equipo, $result[0]['id_equipo']); + } +} +``` + +- [ ] **Paso 3: Ejecutar el test (debe fallar)** + +```bash +php artisan test tests/Unit/AI/Tools/ListarEquiposToolTest.php +``` + +Resultado esperado: `FAIL` — clase `ListarEquiposTool` no existe. + +- [ ] **Paso 4: Implementar `app/AI/Tools/ListarEquiposTool.php`** + +```php +join('torneo_equipo', 'equipos.id_equipo', '=', 'torneo_equipo.id_equipo') + ->where('torneo_equipo.id_torneo', $id_torneo); + + if ($grupo !== null) { + $query->where('torneo_equipo.grupo', $grupo); + } + + $query->select('equipos.id_equipo', 'equipos.categoria', 'equipos.division', 'equipos.id_club'); + } + + $equipos = $query->get()->map(fn($e) => [ + 'id_equipo' => $e->id_equipo, + 'categoria' => $e->categoria, + 'division' => $e->division, + 'club' => $e->club?->nombre, + ]); + + return json_encode($equipos); + } +} +``` + +- [ ] **Paso 5: Ejecutar el test (debe pasar)** + +```bash +php artisan test tests/Unit/AI/Tools/ListarEquiposToolTest.php +``` + +Resultado esperado: `PASS` — 2 tests, 0 fallos. + +- [ ] **Paso 6: Escribir el test de ListarEventosTool** + +Crear `tests/Unit/AI/Tools/ListarEventosToolTest.php`: + +```php + 1], ['nombre' => 'Club A']); + Club::firstOrCreate(['id_club' => 2], ['nombre' => 'Club B']); + $eq1 = Equipo::firstOrCreate(['id_club' => 1, 'categoria' => 'Primera', 'division' => 'A']); + $eq2 = Equipo::firstOrCreate(['id_club' => 2, 'categoria' => 'Primera', 'division' => 'A']); + $torneo = Torneo::firstOrCreate(['nombre' => 'Torneo Test'], ['fecha_inicio' => now(), 'fecha_fin' => now()->addMonths(3)]); + + return Evento::create(array_merge([ + 'id_evento' => (string) Str::uuid(), + 'id_equipo_local' => $eq1->id_equipo, + 'id_equipo_visitante' => $eq2->id_equipo, + 'fecha_evento' => '2025-06-15', + 'hora_inicio' => '20:00:00', + 'hora_fin' => '22:00:00', + 'sede' => 'Estadio Test', + 'id_torneo' => $torneo->id, + 'precio' => 0, + 'fase' => 0, + ], $overrides)); + } + + public function test_retorna_eventos_sin_filtro(): void + { + $this->crearEvento(); + + $tool = new ListarEventosTool(); + $result = json_decode($tool(), true); + + $this->assertCount(1, $result); + $this->assertEquals('2025-06-15', $result[0]['fecha']); + } + + public function test_filtra_por_rango_de_fechas(): void + { + $this->crearEvento(['fecha_evento' => '2025-06-01', 'id_evento' => (string) Str::uuid()]); + $this->crearEvento(['fecha_evento' => '2025-07-01', 'id_evento' => (string) Str::uuid()]); + + $tool = new ListarEventosTool(); + $result = json_decode($tool(fecha_desde: '2025-07-01', fecha_hasta: '2025-07-31'), true); + + $this->assertCount(1, $result); + $this->assertEquals('2025-07-01', $result[0]['fecha']); + } +} +``` + +- [ ] **Paso 7: Ejecutar el test (debe fallar)** + +```bash +php artisan test tests/Unit/AI/Tools/ListarEventosToolTest.php +``` + +Resultado esperado: `FAIL` — clase `ListarEventosTool` no existe. + +- [ ] **Paso 8: Implementar `app/AI/Tools/ListarEventosTool.php`** + +```php +whereNull('deleted_at'); + + if ($fecha_desde) { + $query->whereDate('fecha_evento', '>=', $fecha_desde); + } + if ($fecha_hasta) { + $query->whereDate('fecha_evento', '<=', $fecha_hasta); + } + if ($id_torneo) { + $query->where('id_torneo', $id_torneo); + } + + $eventos = $query->orderBy('fecha_evento')->get()->map(fn($e) => [ + 'id_evento' => $e->id_evento, + 'fecha' => $e->fecha_evento?->format('Y-m-d'), + 'hora' => $e->hora_inicio?->format('H:i'), + 'local' => $e->equipoLocal?->club?->nombre, + 'visitante' => $e->equipoVisitante?->club?->nombre, + 'marcador_local' => $e->marcador_local, + 'marcador_visitante' => $e->marcador_visitante, + 'sede' => $e->sede, + ]); + + return json_encode($eventos); + } +} +``` + +- [ ] **Paso 9: Ejecutar ambos tests** + +```bash +php artisan test tests/Unit/AI/Tools/ListarEquiposToolTest.php tests/Unit/AI/Tools/ListarEventosToolTest.php +``` + +Resultado esperado: `PASS` — 4 tests, 0 fallos. + +- [ ] **Paso 10: Commit** + +```bash +git add app/AI/Tools/ListarEquiposTool.php app/AI/Tools/ListarEventosTool.php \ + tests/Unit/AI/Tools/ListarEquiposToolTest.php tests/Unit/AI/Tools/ListarEventosToolTest.php +git commit -m "feat: add ListarEquipos and ListarEventos read-only tools" +``` + +--- + +## Tarea 4: Tool CrearPartido + +**Archivos:** +- Create: `app/AI/Tools/CrearPartidoTool.php` +- Create: `tests/Unit/AI/Tools/CrearPartidoToolTest.php` + +- [ ] **Paso 1: Escribir el test** + +Crear `tests/Unit/AI/Tools/CrearPartidoToolTest.php`: + +```php + 1, 'nombre' => 'Club A']); + Club::create(['id_club' => 2, 'nombre' => 'Club B']); + $eq1 = Equipo::create(['id_club' => 1, 'categoria' => 'Primera', 'division' => 'A']); + $eq2 = Equipo::create(['id_club' => 2, 'categoria' => 'Primera', 'division' => 'A']); + $torneo = Torneo::create(['nombre' => 'Torneo', 'fecha_inicio' => now(), 'fecha_fin' => now()->addMonths(3)]); + + $tool = new CrearPartidoTool(); + $result = json_decode($tool( + id_equipo_local: $eq1->id_equipo, + id_equipo_visitante: $eq2->id_equipo, + fecha_evento: '2025-08-10', + hora_inicio: '20:00', + hora_fin: '22:00', + sede: 'Estadio Municipal', + id_torneo: $torneo->id + ), true); + + $this->assertTrue($result['success']); + $this->assertDatabaseHas('eventos', [ + 'sede' => 'Estadio Municipal', + 'id_equipo_local' => $eq1->id_equipo, + ]); + } +} +``` + +- [ ] **Paso 2: Ejecutar el test (debe fallar)** + +```bash +php artisan test tests/Unit/AI/Tools/CrearPartidoToolTest.php +``` + +Resultado esperado: `FAIL` + +- [ ] **Paso 3: Implementar `app/AI/Tools/CrearPartidoTool.php`** + +```php + (string) Str::uuid(), + 'id_equipo_local' => $id_equipo_local, + 'id_equipo_visitante' => $id_equipo_visitante, + 'fecha_evento' => $fecha_evento, + 'hora_inicio' => $hora_inicio . ':00', + 'hora_fin' => $hora_fin . ':00', + 'sede' => $sede, + 'id_torneo' => $id_torneo, + 'precio' => $precio ?? 0, + 'fase' => Evento::FASE_REGULAR, + ]); + + return json_encode([ + 'success' => true, + 'id_evento' => $evento->id_evento, + 'mensaje' => "Partido creado correctamente. ID: {$evento->id_evento}", + ]); + } +} +``` + +- [ ] **Paso 4: Ejecutar el test (debe pasar)** + +```bash +php artisan test tests/Unit/AI/Tools/CrearPartidoToolTest.php +``` + +Resultado esperado: `PASS` + +- [ ] **Paso 5: Commit** + +```bash +git add app/AI/Tools/CrearPartidoTool.php tests/Unit/AI/Tools/CrearPartidoToolTest.php +git commit -m "feat: add CrearPartidoTool" +``` + +--- + +## Tarea 5: Tool CargarPuntaje + +**Archivos:** +- Create: `app/AI/Tools/CargarPuntajeTool.php` +- Create: `tests/Unit/AI/Tools/CargarPuntajeToolTest.php` + +- [ ] **Paso 1: Escribir el test** + +Crear `tests/Unit/AI/Tools/CargarPuntajeToolTest.php`: + +```php + 1, 'nombre' => 'Club A']); + Club::create(['id_club' => 2, 'nombre' => 'Club B']); + $eq1 = Equipo::create(['id_club' => 1, 'categoria' => 'Primera', 'division' => 'A']); + $eq2 = Equipo::create(['id_club' => 2, 'categoria' => 'Primera', 'division' => 'A']); + $torneo = Torneo::create(['nombre' => 'Torneo', 'fecha_inicio' => now(), 'fecha_fin' => now()->addMonths(3)]); + + return Evento::create([ + 'id_evento' => (string) Str::uuid(), + 'id_equipo_local' => $eq1->id_equipo, + 'id_equipo_visitante' => $eq2->id_equipo, + 'fecha_evento' => '2025-08-10', + 'hora_inicio' => '20:00:00', + 'hora_fin' => '22:00:00', + 'sede' => 'Estadio', + 'id_torneo' => $torneo->id, + 'precio' => 0, + 'fase' => 0, + ]); + } + + public function test_actualiza_puntaje_correctamente(): void + { + $evento = $this->crearEvento(); + + $tool = new CargarPuntajeTool(); + $result = json_decode($tool( + id_evento: $evento->id_evento, + marcador_local: 85, + marcador_visitante: 72 + ), true); + + $this->assertTrue($result['success']); + $this->assertDatabaseHas('eventos', [ + 'id_evento' => $evento->id_evento, + 'marcador_local' => 85, + 'marcador_visitante' => 72, + ]); + } + + public function test_retorna_error_si_evento_no_existe(): void + { + $tool = new CargarPuntajeTool(); + $result = json_decode($tool( + id_evento: 'uuid-inexistente', + marcador_local: 10, + marcador_visitante: 20 + ), true); + + $this->assertFalse($result['success']); + $this->assertStringContainsString('no encontrado', $result['error']); + } +} +``` + +- [ ] **Paso 2: Ejecutar el test (debe fallar)** + +```bash +php artisan test tests/Unit/AI/Tools/CargarPuntajeToolTest.php +``` + +Resultado esperado: `FAIL` + +- [ ] **Paso 3: Implementar `app/AI/Tools/CargarPuntajeTool.php`** + +```php + false, + 'error' => "Evento '{$id_evento}' no encontrado.", + ]); + } + + $evento->update([ + 'marcador_local' => $marcador_local, + 'marcador_visitante' => $marcador_visitante, + ]); + + return json_encode([ + 'success' => true, + 'mensaje' => "Puntaje cargado: {$marcador_local} - {$marcador_visitante}", + ]); + } +} +``` + +- [ ] **Paso 4: Ejecutar el test (debe pasar)** + +```bash +php artisan test tests/Unit/AI/Tools/CargarPuntajeToolTest.php +``` + +Resultado esperado: `PASS` — 2 tests. + +- [ ] **Paso 5: Commit** + +```bash +git add app/AI/Tools/CargarPuntajeTool.php tests/Unit/AI/Tools/CargarPuntajeToolTest.php +git commit -m "feat: add CargarPuntajeTool" +``` + +--- + +## Tarea 6: Tool RedactarNoticia + +**Archivos:** +- Create: `app/AI/Tools/RedactarNoticiaTool.php` +- Create: `tests/Unit/AI/Tools/RedactarNoticiaToolTest.php` + +- [ ] **Paso 1: Escribir el test** + +Crear `tests/Unit/AI/Tools/RedactarNoticiaToolTest.php`: + +```php +assertTrue($result['success']); + $this->assertDatabaseHas('noticias', ['titulo' => 'Gran partido en la final']); + } + + public function test_crea_noticia_con_torneo_y_categoria(): void + { + $tool = new RedactarNoticiaTool(); + $result = json_decode($tool( + titulo: 'Resumen jornada', + contenido: 'Resumen de la jornada 5.', + id_torneo: 1, + categoria: 'resultados' + ), true); + + $this->assertTrue($result['success']); + $this->assertDatabaseHas('noticias', [ + 'titulo' => 'Resumen jornada', + 'categoria' => 'resultados', + ]); + } +} +``` + +- [ ] **Paso 2: Ejecutar el test (debe fallar)** + +```bash +php artisan test tests/Unit/AI/Tools/RedactarNoticiaToolTest.php +``` + +Resultado esperado: `FAIL` + +- [ ] **Paso 3: Implementar `app/AI/Tools/RedactarNoticiaTool.php`** + +```php + $titulo, + 'contenido' => $contenido, + 'fecha' => now(), + 'id_torneo' => $id_torneo, + 'categoria' => $categoria, + ]); + + return json_encode([ + 'success' => true, + 'id' => $noticia->id, + 'mensaje' => "Noticia creada: {$noticia->titulo}", + ]); + } +} +``` + +- [ ] **Paso 4: Ejecutar el test (debe pasar)** + +```bash +php artisan test tests/Unit/AI/Tools/RedactarNoticiaToolTest.php +``` + +Resultado esperado: `PASS` — 2 tests. + +- [ ] **Paso 5: Ejecutar todos los tests de tools** + +```bash +php artisan test tests/Unit/AI/Tools/ +``` + +Resultado esperado: `PASS` — 9 tests, 0 fallos. + +- [ ] **Paso 6: Commit** + +```bash +git add app/AI/Tools/RedactarNoticiaTool.php tests/Unit/AI/Tools/RedactarNoticiaToolTest.php +git commit -m "feat: add RedactarNoticiaTool — all 5 tools complete" +``` + +--- + +## Tarea 7: System Prompts + +**Archivos:** +- Create: `app/AI/Prompts/SystemPromptAdmin.php` +- Create: `app/AI/Prompts/SystemPromptPublic.php` + +- [ ] **Paso 1: Crear `app/AI/Prompts/SystemPromptAdmin.php`** + +```php +loadManual(); + + return <<chatAdmin($message, $threadId); + } + + return $this->chatPublic($message); + } + + private function chatPublic(string $message): array + { + $history = session('agent_messages', []); + $messages = $this->hydrate($history); + $messages[] = new UserMessage($message); + + $response = Prism::text() + ->using(Provider::Gemini, 'gemini-1.5-flash') + ->withSystemPrompt((new SystemPromptPublic())->build()) + ->withMessages($messages) + ->generate(); + + $reply = $response->text; + + $history[] = ['role' => 'user', 'content' => $message]; + $history[] = ['role' => 'assistant', 'content' => $reply]; + session(['agent_messages' => $history]); + + return ['reply' => $reply]; + } + + private function chatAdmin(string $message, ?string $threadId): array + { + $adminId = (int) session('admin_id', 0); + $thread = AgentThread::findOrCreateForAdmin($threadId, $adminId); + + $messages = $this->hydrate($thread->messages ?? []); + $messages[] = new UserMessage($message); + + $response = Prism::text() + ->using(Provider::Gemini, 'gemini-1.5-flash') + ->withSystemPrompt((new SystemPromptAdmin())->build()) + ->withMessages($messages) + ->withTools($this->buildAdminTools()) + ->generate(); + + $reply = $response->text; + + $stored = $thread->messages ?? []; + $stored[] = ['role' => 'user', 'content' => $message]; + $stored[] = ['role' => 'assistant', 'content' => $reply]; + $thread->messages = $stored; + $thread->save(); + + return ['reply' => $reply, 'thread_id' => $thread->thread_id]; + } + + private function hydrate(array $stored): array + { + return collect($stored)->map(fn($m) => $m['role'] === 'user' + ? new UserMessage($m['content']) + : new AssistantMessage($m['content']) + )->all(); + } + + private function buildAdminTools(): array + { + return [ + Tool::as('listar_equipos') + ->for('Lista los equipos. Filtrá por id_torneo y/o grupo para obtener los IDs correctos antes de crear partidos.') + ->withNumberParameter('id_torneo', 'ID del torneo (opcional)', false) + ->withStringParameter('grupo', 'Nombre del grupo dentro del torneo (opcional)', false) + ->using(new ListarEquiposTool()), + + Tool::as('listar_eventos') + ->for('Lista los partidos. Filtrá por rango de fechas (Y-m-d) o id_torneo para obtener los IDs antes de cargar puntajes.') + ->withStringParameter('fecha_desde', 'Fecha desde en formato Y-m-d (opcional)', false) + ->withStringParameter('fecha_hasta', 'Fecha hasta en formato Y-m-d (opcional)', false) + ->withNumberParameter('id_torneo', 'ID del torneo (opcional)', false) + ->using(new ListarEventosTool()), + + Tool::as('crear_partido') + ->for('Crea un nuevo partido en el sistema. Podés llamar esta tool múltiples veces para crear varios partidos.') + ->withNumberParameter('id_equipo_local', 'ID del equipo local') + ->withNumberParameter('id_equipo_visitante', 'ID del equipo visitante') + ->withStringParameter('fecha_evento', 'Fecha del partido en formato Y-m-d') + ->withStringParameter('hora_inicio', 'Hora de inicio en formato H:i (ej: 20:00)') + ->withStringParameter('hora_fin', 'Hora de fin en formato H:i (ej: 22:00)') + ->withStringParameter('sede', 'Lugar donde se juega el partido') + ->withNumberParameter('id_torneo', 'ID del torneo al que pertenece el partido') + ->withNumberParameter('precio', 'Precio de la entrada en pesos (0 si es gratis)', false) + ->using(new CrearPartidoTool()), + + Tool::as('cargar_puntaje') + ->for('Carga o actualiza el puntaje de un partido existente. Podés llamar esta tool múltiples veces para cargar puntajes de varios partidos.') + ->withStringParameter('id_evento', 'ID del evento (UUID — usá listar_eventos para obtenerlo)') + ->withNumberParameter('marcador_local', 'Puntos del equipo local') + ->withNumberParameter('marcador_visitante', 'Puntos del equipo visitante') + ->using(new CargarPuntajeTool()), + + Tool::as('redactar_noticia') + ->for('Crea una noticia en el sistema. El contenido puede ser HTML o texto plano.') + ->withStringParameter('titulo', 'Título de la noticia') + ->withStringParameter('contenido', 'Contenido completo de la noticia') + ->withNumberParameter('id_torneo', 'ID del torneo relacionado (opcional)', false) + ->withStringParameter('categoria', 'Categoría de la noticia (opcional)', false) + ->using(new RedactarNoticiaTool()), + ]; + } +} +``` + +- [ ] **Paso 2: Verificar que no hay errores de sintaxis** + +```bash +php artisan tinker --execute="new App\Services\GeniusAgentService(); echo 'OK';" +``` + +Resultado esperado: `OK` + +- [ ] **Paso 3: Commit** + +```bash +git add app/Services/GeniusAgentService.php +git commit -m "feat: add GeniusAgentService with public/admin chat modes" +``` + +--- + +## Tarea 9: GeniusAgentController y Route + +**Archivos:** +- Create: `app/Http/Controllers/GeniusAgentController.php` +- Create: `tests/Feature/GeniusAgentControllerTest.php` +- Modify: `routes/web.php` + +- [ ] **Paso 1: Escribir el test del controller** + +Crear `tests/Feature/GeniusAgentControllerTest.php`: + +```php +postJson('/agent/chat', ['message' => '']); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['message']); + } + + public function test_rechaza_mensaje_demasiado_largo(): void + { + $response = $this->postJson('/agent/chat', ['message' => str_repeat('a', 1001)]); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['message']); + } + + public function test_usuario_publico_recibe_respuesta(): void + { + $this->mock(GeniusAgentService::class, function ($mock) { + $mock->shouldReceive('chat') + ->once() + ->with('Hola', false, null) + ->andReturn(['reply' => 'Hola desde el agente']); + }); + + $response = $this->postJson('/agent/chat', ['message' => 'Hola']); + + $response->assertStatus(200) + ->assertJson(['reply' => 'Hola desde el agente']); + } + + public function test_admin_recibe_respuesta_con_thread_id(): void + { + $this->mock(GeniusAgentService::class, function ($mock) { + $mock->shouldReceive('chat') + ->once() + ->andReturn(['reply' => 'Listo', 'thread_id' => 'test-uuid']); + }); + + $response = $this->withSession(['admin_logged_in' => true]) + ->postJson('/agent/chat', ['message' => 'Crear partido']); + + $response->assertStatus(200) + ->assertJsonStructure(['reply', 'thread_id']); + } + + public function test_retorna_error_generico_si_el_servicio_falla(): void + { + $this->mock(GeniusAgentService::class, function ($mock) { + $mock->shouldReceive('chat') + ->once() + ->andThrow(new \Exception('API timeout')); + }); + + $response = $this->postJson('/agent/chat', ['message' => 'Hola']); + + $response->assertStatus(500) + ->assertJson(['error' => 'El agente no responde, reintentá en un momento.']); + } +} +``` + +- [ ] **Paso 2: Ejecutar el test (debe fallar)** + +```bash +php artisan test tests/Feature/GeniusAgentControllerTest.php +``` + +Resultado esperado: `FAIL` — ruta no existe. + +- [ ] **Paso 3: Crear el controller `app/Http/Controllers/GeniusAgentController.php`** + +```php +validate([ + 'message' => 'required|string|max:1000', + 'thread_id' => 'nullable|string|max:36', + ]); + + $isAdmin = (bool) session('admin_logged_in'); + + try { + $result = $this->service->chat( + $data['message'], + $isAdmin, + $data['thread_id'] ?? null + ); + + return response()->json($result); + } catch (\Throwable $e) { + \Log::error('GeniusAgent error: ' . $e->getMessage()); + + return response()->json( + ['error' => 'El agente no responde, reintentá en un momento.'], + 500 + ); + } + } +} +``` + +- [ ] **Paso 4: Agregar la ruta en `routes/web.php`** + +Al final de las importaciones en `routes/web.php`, agregar: +```php +use App\Http\Controllers\GeniusAgentController; +``` + +Al final del archivo, agregar: +```php +Route::post('/agent/chat', [GeniusAgentController::class, 'chat']) + ->name('agent.chat') + ->middleware('throttle:20,1'); +``` + +- [ ] **Paso 5: Ejecutar el test (debe pasar)** + +```bash +php artisan test tests/Feature/GeniusAgentControllerTest.php +``` + +Resultado esperado: `PASS` — 4 tests. + +- [ ] **Paso 6: Commit** + +```bash +git add app/Http/Controllers/GeniusAgentController.php \ + tests/Feature/GeniusAgentControllerTest.php \ + routes/web.php +git commit -m "feat: add GeniusAgentController and POST /agent/chat route" +``` + +--- + +## Tarea 10: Comando PurgeAgentThreads + +**Archivos:** +- Create: `app/Console/Commands/PurgeAgentThreads.php` +- Modify: `routes/console.php` + +- [ ] **Paso 1: Crear el command** + +```bash +php artisan make:command PurgeAgentThreads +``` + +Abrir `app/Console/Commands/PurgeAgentThreads.php` y reemplazar con: + +```php +delete(); + $this->info("Eliminados {$count} threads expirados."); + + return self::SUCCESS; + } +} +``` + +- [ ] **Paso 2: Registrar en el schedule (`routes/console.php`)** + +Abrir `routes/console.php` y agregar al final: + +```php +use Illuminate\Support\Facades\Schedule; + +Schedule::command('agent:purge-threads')->daily(); +``` + +Si `Schedule::` ya está importado, omitir el `use`. + +- [ ] **Paso 3: Verificar que el command funciona** + +```bash +php artisan agent:purge-threads +``` + +Resultado esperado: `Eliminados 0 threads expirados.` + +- [ ] **Paso 4: Commit** + +```bash +git add app/Console/Commands/PurgeAgentThreads.php routes/console.php +git commit -m "feat: add agent:purge-threads command with daily schedule" +``` + +--- + +## Tarea 11: Chat Bubble — Frontend y Wiring + +**Archivos:** +- Create: `resources/views/components/genius-chat.blade.php` +- Modify: `resources/views/layouts/app.blade.php` + +- [ ] **Paso 1: Crear `resources/views/components/genius-chat.blade.php`** + +```html +
+ + {{-- Botón flotante --}} + + + {{-- Panel del chat --}} + +
+ + +``` + +- [ ] **Paso 2: Incluir el componente en `resources/views/layouts/app.blade.php`** + +Buscar la línea `` al final del archivo y agregar el include justo antes: + +```html + @include('components.genius-chat') + +``` + +Si ya hay scripts de Bootstrap u otros justo antes de ``, agregar el include después de ellos pero antes de ``. + +- [ ] **Paso 3: Agregar CSRF meta tag si no existe** + +En `resources/views/layouts/app.blade.php`, dentro de ``, verificar que existe: +```html + +``` + +Si no existe, agregarlo debajo de ``. + +- [ ] **Paso 4: Verificar visualmente** + +```bash +php artisan serve +``` + +Abrir `http://localhost:8000` y verificar: +- El botón rojo con icono `bi-stars` aparece en la esquina inferior derecha +- Click abre el panel +- El campo de texto acepta input +- Cerrando y abriendo el panel no pierde mensajes de la sesión + +- [ ] **Paso 5: Commit final** + +```bash +git add resources/views/components/genius-chat.blade.php resources/views/layouts/app.blade.php +git commit -m "feat: add genius-chat bubble component and wire into layout" +``` + +--- + +## Verificación Final + +- [ ] **Ejecutar todos los tests** + +```bash +php artisan test +``` + +Resultado esperado: todos los tests del proyecto pasan (mínimo los 13 nuevos). + +- [ ] **Test de smoke manual (público)** + +1. Abrir el sitio sin loguearse +2. Clickear el botón del agente +3. Escribir: "¿Dónde veo mis QRs?" +4. Verificar que responde en español usando el manual + +- [ ] **Test de smoke manual (admin)** + +1. Loguearse como admin +2. Clickear el botón del agente +3. Escribir: "Listame los equipos del torneo 1" +4. Verificar que la tool se ejecuta y retorna los equipos + +- [ ] **Commit de cierre** + +```bash +git add . +git commit -m "feat: OnAPB Genius Agent — complete implementation" +``` + +--- + +## Comandos de Deploy en Hostinger + +```bash +# 1. Subir archivos y correr en SSH: +composer install --no-dev --optimize-autoloader +php artisan migrate --force +php artisan config:cache +php artisan route:cache +php artisan view:cache + +# 2. Configurar cron en hPanel de Hostinger (una sola vez): +# Reemplazar TU_USUARIO con tu usuario de Hostinger: +# 0 3 * * * cd /home/TU_USUARIO/public_html && php artisan agent:purge-threads >> /dev/null 2>&1 + +# 3. Verificar que la API key está en el .env de producción: +# GEMINI_API_KEY=tu_api_key_aqui +``` diff --git a/docs/superpowers/specs/2026-04-09-genius-agent-design.md b/docs/superpowers/specs/2026-04-09-genius-agent-design.md new file mode 100644 index 0000000..e25721b --- /dev/null +++ b/docs/superpowers/specs/2026-04-09-genius-agent-design.md @@ -0,0 +1,244 @@ +# OnAPB Genius Agent — Spec de Diseño + +**Fecha:** 2026-04-09 +**Rama:** `feature/genius-agent` +**Stack:** Laravel 12, Prism PHP (prism-php/prism), Google Gemini 1.5 Flash, MySQL, Alpine.js + +--- + +## 1. Objetivo + +Implementar un agente de IA conversacional ("OnAPB Genius") integrado en onapb.com con dos modos de operación según el rol del usuario: + +- **Público** (no logueado, aficionado, jugador): asistente de navegación y consultas usando el `MANUAL_USUARIO.md` como contexto RAG simplificado. Sin tools. Sin persistencia entre sesiones. +- **Admin** (SuperAdmin rol=1 / GeneralAdmin rol=2): automatización de tareas mediante function calling (Tools). Historial persistente en MySQL, auto-purgado a los 30 días. + +--- + +## 2. Decisiones de Arquitectura + +| Decisión | Elección | Razón | +|---|---|---| +| SDK de AI | `prism-php/prism` | Soporte probado de Gemini 1.5 Flash + tools. Más estable que `laravel/ai` (nuevo). | +| Modelo | `gemini-1.5-flash` | Velocidad (2–5s), costo bajo, function calling. | +| Streaming | No — JSON completo | Hosting compartido Hostinger con límites PHP desconocidos. Evita problemas de output buffer. | +| Memoria admin | MySQL (`agent_threads`) | Sin Redis. JSON column para mensajes. Purge automático 30 días. | +| Memoria público | Session PHP | Stateless entre sesiones. Simple, sin overhead de BD. | +| Tools | Solo si admin_logged_in | Validado en `GeniusAgentService`, no en Gemini. | + +--- + +## 3. Estructura de Archivos + +``` +app/ +├── AI/ +│ ├── Tools/ +│ │ ├── CrearPartidoTool.php +│ │ ├── CargarPuntajeTool.php +│ │ ├── RedactarNoticiaTool.php +│ │ ├── ListarEquiposTool.php +│ │ └── ListarEventosTool.php +│ └── Prompts/ +│ ├── SystemPromptAdmin.php +│ └── SystemPromptPublic.php +├── Services/ +│ └── GeniusAgentService.php +├── Http/Controllers/ +│ └── GeniusAgentController.php +├── Models/ +│ └── AgentThread.php +└── Console/Commands/ + └── PurgeAgentThreads.php + +database/migrations/ +└── xxxx_create_agent_threads_table.php + +resources/views/components/ +└── genius-chat.blade.php + +routes/web.php +└── POST /agent/chat (throttle: 20/min por IP) +``` + +--- + +## 4. Schema de Base de Datos + +```sql +CREATE TABLE agent_threads ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + thread_id VARCHAR(36) NOT NULL UNIQUE, -- UUID generado en frontend + admin_id INT NOT NULL, -- session('admin_id') + messages JSON NOT NULL, -- array [{role, content}] + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + expires_at TIMESTAMP NOT NULL -- created_at + 30 días +); +``` + +--- + +## 5. Tools Disponibles (Admin) + +Cada tool implementa la interfaz Prism `Tool`. El agent loop permite que Gemini llame la misma tool N veces para operaciones batch (ej. cargar puntajes de todos los partidos de una jornada). + +| Tool | Parámetros | Acción en BD | +|---|---|---| +| `CrearPartidoTool` | `id_equipo_local`, `id_equipo_visitante`, `fecha_evento`, `hora_inicio`, `hora_fin`, `sede`, `id_torneo` | `Evento::create(...)` | +| `CargarPuntajeTool` | `id_evento`, `marcador_local`, `marcador_visitante` | `Evento::find()->update(...)` | +| `RedactarNoticiaTool` | `titulo`, `contenido`, `id_torneo?`, `categoria?` | `Noticia::create(...)` | +| `ListarEquiposTool` | `id_torneo?`, `id_club?`, `grupo?` | `Equipo::query()` con join a pivot `torneo_equipo` filtrando por `grupo` (solo lectura) | +| `ListarEventosTool` | `fecha_desde?`, `fecha_hasta?`, `id_torneo?` | `Evento::query()->get()` (solo lectura) | + +**Nota sobre `grupo`:** No es un modelo separado. Es una columna en la tabla pivot `torneo_equipo` (relación `Torneo::equipos()->withPivot('grupo')`). `ListarEquiposTool` filtra con `wherePivot('grupo', $grupo)`. + +**Agregar una nueva tool en el futuro:** +1. Crear `app/AI/Tools/NuevaTool.php` +2. Registrarla en `GeniusAgentService::getAdminTools()` + +--- + +## 6. Flujo de Datos + +### Usuario público +``` +POST /agent/chat { message } +→ session()->get('agent_messages', []) +→ GeniusAgentService::chatPublic(message, history) + → System prompt: navegación + MANUAL_USUARIO.md completo como contexto (el archivo es pequeño; si crece >50KB usar solo las primeras 200 líneas) + → Prism → Gemini (sin tools) + → Respuesta de texto +→ session()->put('agent_messages', [...]) +→ return JSON { reply } +``` + +### Admin +``` +POST /agent/chat { message, thread_id? } +→ AgentThread::findOrCreate(thread_id, admin_id) +→ GeniusAgentService::chatAdmin(message, thread) + → System prompt: automatización de tareas OnAPB + → Prism → Gemini con tools + → [si Gemini llama tool] → Tool::handle() → resultado + → [Gemini puede llamar N tools] → agent loop + → Respuesta final de texto +→ thread->appendMessages([...]) +→ thread->save() +→ return JSON { reply, thread_id } +``` + +--- + +## 7. Seguridad + +- **Tools bloqueadas a no-admins:** `GeniusAgentService` valida `session('admin_logged_in')` antes de incluir tools. +- **Rate limiting:** `throttle:20,1` en la ruta `/agent/chat`. +- **Validación de input:** `message` requerido, string, máx. 1000 caracteres. +- **Aislamiento de threads:** cada thread valida `admin_id = session('admin_id')`. No hay acceso cruzado entre admins. +- **API key:** solo en `.env` (`GEMINI_API_KEY`). Nunca en código ni en BD. + +--- + +## 8. Manejo de Errores + +| Caso | Comportamiento | +|---|---| +| Gemini timeout / 5xx | `catch RequestException` → JSON `{ error: "El agente no responde, reintentá en un momento." }` | +| Tool falla (ej. evento no existe) | Tool retorna `{ error: "..." }` → Gemini lo incorpora en su respuesta | +| Thread expirado / no encontrado | Se crea un nuevo thread automáticamente | +| API key inválida / quota excedida | Log en Laravel + respuesta genérica (sin exponer detalles de la API) | +| Timeout de Hostinger | `set_time_limit(120)` al inicio del controller action | + +--- + +## 9. Purge de Threads + +Comando `php artisan agent:purge-threads` que elimina `agent_threads` donde `expires_at < NOW()`. + +**Schedule:** Se registra en `routes/console.php` para ejecutarse diariamente. En Hostinger sin queue worker, se puede configurar como cron en hPanel: + +``` +# Reemplazar /home/TU_USUARIO/public_html con la ruta real de tu cuenta Hostinger +0 3 * * * cd /home/TU_USUARIO/public_html && php artisan agent:purge-threads >> /dev/null 2>&1 +``` + +--- + +## 10. Frontend — Chat Bubble + +Componente Blade `genius-chat.blade.php` incluido al final de `resources/views/layouts/app.blade.php`: + +- Botón flotante (bottom-right) con ícono de chat +- Panel slide-up con historial de mensajes de la sesión actual +- Alpine.js para estado local (open/closed, messages, loading spinner) +- Tailwind para estilos (sin dependencias adicionales) +- Rol-aware: el frontend no diferencia, la diferencia la hace el backend + +--- + +## 11. Comandos de Deploy + +### Instalación inicial (local y producción) + +```bash +# 1. Instalar Prism PHP +composer require prism-php/prism + +# 2. Publicar config de Prism (opcional) +php artisan vendor:publish --provider="EchoLabs\Prism\PrismServiceProvider" + +# 3. Agregar en .env +GEMINI_API_KEY=tu_api_key_aqui + +# 4. Ejecutar migration de agent_threads +php artisan migrate + +# 5. Registrar el comando de purge (se hace automáticamente con el código) +# Verificar que está en routes/console.php + +# 6. Limpiar cachés después del deploy +php artisan config:clear +php artisan cache:clear +php artisan view:clear +php artisan route:clear + +# 7. Re-cachear para producción +php artisan config:cache +php artisan route:cache +php artisan view:cache +``` + +### Comandos de mantenimiento + +```bash +# Purge manual de threads expirados +php artisan agent:purge-threads + +# Ver logs del agente +php artisan pail --filter="GeniusAgent" + +# Limpiar todos los threads (emergencia) +php artisan tinker --execute="App\Models\AgentThread::truncate();" +``` + +### Deploy en Hostinger (via SSH o File Manager) + +```bash +# Subir archivos nuevos y correr: +php artisan migrate --force +php artisan config:cache +php artisan route:cache +php artisan view:cache +``` + +--- + +## 12. Configuración `.env` requerida + +```env +GEMINI_API_KEY=tu_api_key_de_google_ai_studio + +# Opcional: ajustar timeout HTTP de Prism +PRISM_HTTP_TIMEOUT=30 +``` diff --git a/docs/superpowers/specs/2026-04-24-pagos-macro-design.md b/docs/superpowers/specs/2026-04-24-pagos-macro-design.md new file mode 100644 index 0000000..3758e8c --- /dev/null +++ b/docs/superpowers/specs/2026-04-24-pagos-macro-design.md @@ -0,0 +1,376 @@ +# Diseño: Sistema de Pagos con Banco Macro + +**Fecha:** 2026-04-24 +**Estado:** Aprobado por el usuario — listo para plan de implementación +**Proyecto:** OnAPB v2 (Laravel / Hostinger Business) + +--- + +## Contexto + +OnAPB v2 es el sistema de gestión de la Asociación Paranaense de Básquet. Se busca implementar un sistema de cobros digitales integrado con Banco Macro (Macro Click de Pago — Botón Integrado), centralizado en la cuenta de la APB. + +- Framework: Laravel (PHP) +- Roles del sistema: super admin (role=1), admin de club (role=2), jugadores/aficionados (usuarios registrados), anónimos +- Existía una integración con MercadoPago que fue eliminada +- Las credenciales del Botón Integrado de Macro fueron solicitadas el 2026-04-23 y están pendientes de recibir +- Contacto Macro: Diego Dallanora — diegodallanora@macro.com.ar — +54 3794 15-0073 + +--- + +## Decisiones de diseño + +| Decisión | Resolución | +|---|---| +| Pasarela de pago | Macro Click de Pago — **Botón Integrado** | +| Quién recauda | **La APB centraliza** todos los pagos (un solo merchant Macro) | +| Entrega tienda | **Retiro físico en sede** (sin envíos) | +| Sanciones | Super admin las carga manualmente sobre un jugador | +| Modelo de datos | **Tabla polimórfica de pagos** (Opción A) | + +## Comisiones Macro (propuesta comercial vigente) + +| Medio | Comisión | Acreditación | +|---|---|---| +| Tarjeta de crédito | 3.05% | 18 días hábiles | +| Tarjeta de crédito en cuotas | 3.05% | 18 días hábiles | +| Tarjeta de débito | 3.00% | 1 día hábil | +| DEBIN | 3.00% | 1 día hábil | + +--- + +## Descomposición en sub-proyectos + +Implementar en este orden (cada uno depende del anterior): + +1. **Motor de pagos** — integración Macro base, modelo de datos, webhook +2. **Cobros institucionales** — inscripciones, multas/sanciones +3. **Tienda online** — catálogo, carrito, checkout + +--- + +## Sección 1 — Modelo de datos + +### Tablas nuevas + +#### `concepto_pagos` — plantillas configurables por el super admin +| Campo | Tipo | Notas | +|---|---|---| +| `id` | PK | | +| `nombre` | string | ej: "Inscripción Anual Jugador 2026" | +| `descripcion` | text | | +| `monto` | decimal(10,2) | | +| `tipo` | enum | `inscripcion_jugador`, `inscripcion_equipo`, `multa`, `tienda` | +| `temporada` | string nullable | ej: "2026" — para conceptos anuales | +| `activo` | boolean | default true | +| timestamps | | | + +#### `pagos` — registro de transacciones (polimórfico) +| Campo | Tipo | Notas | +|---|---|---| +| `id` | PK | | +| `concepto_pago_id` | FK → concepto_pagos | | +| `pagable_type` | string | App\Models\Jugador / Club / OrdenTienda / Sancion | +| `pagable_id` | unsignedBigInt | | +| `monto` | decimal(10,2) | snapshot al crear el pago | +| `estado` | enum | `pendiente`, `pagado`, `fallido`, `cancelado` | +| `macro_transaction_id` | string nullable | ID devuelto por Macro | +| `macro_payload` | json nullable | respuesta raw de Macro | +| `paid_at` | timestamp nullable | | +| `iniciado_por_type` | string nullable | quién inició (jugador, admin_user, null=anónimo) | +| `iniciado_por_id` | unsignedBigInt nullable | | +| timestamps | | | + +#### `sanciones` — registros disciplinarios +| Campo | Tipo | Notas | +|---|---|---| +| `id` | PK | | +| `jugador_id` | FK → jugadores | | +| `id_club` | FK → clubes | club al momento de la sanción | +| `motivo` | string | | +| `descripcion` | text nullable | | +| `fecha_sancion` | date | | +| `admin_id` | FK → admin_users | quién la cargó | +| timestamps | | | + +El pago de la sanción vive en `pagos` con `pagable_type = App\Models\Sancion`. + +#### `productos` — catálogo de la tienda +| Campo | Tipo | | +|---|---|---| +| `id` | PK | | +| `nombre` | string | | +| `descripcion` | text nullable | | +| `precio` | decimal(10,2) | | +| `stock` | unsignedInt | | +| `imagen` | string nullable | | +| `activo` | boolean | default true | +| timestamps | | | + +#### `ordenes_tienda` — órdenes de compra +| Campo | Tipo | Notas | +|---|---|---| +| `id` | PK | | +| `user_id` | FK nullable → users | null si comprador anónimo | +| `nombre_comprador` | string | capturado en checkout | +| `email_comprador` | string | para enviar comprobante | +| `estado` | enum | `pendiente_pago`, `pagado`, `listo_retiro`, `retirado`, `cancelado` | +| timestamps | | | + +#### `orden_items` — líneas de cada orden +| Campo | Tipo | Notas | +|---|---|---| +| `id` | PK | | +| `orden_id` | FK → ordenes_tienda | | +| `producto_id` | FK → productos | | +| `cantidad` | unsignedInt | | +| `precio_unitario` | decimal(10,2) | snapshot al momento de compra | + +### Relaciones Eloquent +``` +ConceptoPago hasMany Pago +Pago morphTo pagable (Jugador | Club | OrdenTienda | Sancion) +Sancion belongsTo Jugador, Club, AdminUser +OrdenTienda hasMany OrdenItem +OrdenItem belongsTo Producto +``` + +--- + +## Sección 2 — Flujo de pago con Macro (Botón Integrado) + +### Flujo estándar (aplica a todos los conceptos de pago) + +``` +Usuario decide pagar un concepto + ↓ +Sistema crea registro en `pagos` (estado = pendiente) + ↓ +GET /checkout/{pago} +Página con botón Macro embebido (monto + referencia interna precargados) + ↓ +Usuario interactúa con el formulario de Macro +(tarjeta crédito / débito / DEBIN) + ↓ + ┌──────────────────┬─────────────────┐ + ↓ ↓ ↓ + Pago exitoso Pago fallido Abandona + ↓ ↓ ↓ + Macro → /pagos/ Macro → /pagos/ Pago queda pendiente + exitoso fallido (expira por cron) + ↓ +POST /api/macro/webhook ← fuente de verdad + ↓ +Valida firma de Macro + ↓ +Actualiza pago: estado = pagado, paid_at = now(), macro_payload = {...} + ↓ +Dispara evento post-pago según pagable_type + ↓ +Envía email de comprobante al pagador +``` + +**Regla crítica:** El webhook es la fuente de verdad, no la redirección. Las páginas de éxito/fallo son solo UX. + +### Eventos post-pago por tipo + +| `pagable_type` | Acción tras confirmar pago | +|---|---| +| `Jugador` (inscripción) | Marcar jugador como inscripto en la temporada | +| `Club` (inscripción equipo) | Marcar equipo/club como habilitado para participar | +| `Sancion` | Marcar sanción como saldada | +| `OrdenTienda` | Estado → `pagado`; notificar al super admin | + +### Rutas nuevas + +``` +GET /checkout/{pago} → página con botón Macro embebido +GET /pagos/exitoso → pantalla de éxito (UX) +GET /pagos/fallido → pantalla de fallo (UX) +POST /api/macro/webhook → receptor del webhook (excluido de CSRF) +GET /tienda → catálogo público +GET /tienda/carrito → carrito de compras +POST /tienda/checkout → genera orden + pago, redirige a /checkout/{pago} +``` + +### Seguridad del webhook + +- Verificar firma de Macro antes de procesar (algoritmo a confirmar con Macro) +- El monto siempre se toma de `pagos.monto`, nunca del payload entrante +- Idempotente: si llega dos veces el mismo `macro_transaction_id`, no procesar dos veces +- Log de cada webhook recibido para auditoría + +--- + +## Sección 3 — Panel de administración + +### Super admin (role=1) — nuevas secciones + +**A. Conceptos de pago** +- Listado con filtros por tipo y estado (activo/inactivo) +- Crear / editar: nombre, descripción, monto, tipo, temporada, activo +- Desactivar un concepto no elimina ni afecta pagos ya realizados + +**B. Sanciones** +- Formulario: buscar jugador → seleccionar club → ingresar motivo, descripción, fecha → asociar concepto de pago tipo `multa` +- Listado con estado de pago (pendiente / saldada); filtros por club, jugador, estado, fecha + +**C. Tienda** +- CRUD de productos: nombre, descripción, precio, stock, imagen, activo +- Listado de órdenes con gestión de estado: + - `pagado` → "Marcar listo para retirar" → email automático al comprador + - `listo_retiro` → "Marcar como retirado" +- Filtros por estado y fecha + +**D. Transacciones / Recaudación** +- Tabla de todos los `pagos` con filtros: estado, tipo de concepto, rango de fechas +- Totales agrupados por tipo de concepto +- Exportar CSV para conciliación con la plataforma de Macro + +### Admin de club (role=2) — nuevas secciones + +**A. Sanciones del club** +- Lista de sanciones de los jugadores de su club +- Estado de cada sanción (pendiente / saldada) +- Botón para pagar una sanción pendiente (el club abona, no el jugador) + +**B. Inscripciones del club** +- Conceptos de inscripción de equipo disponibles para la temporada vigente +- Botón para pagar inscripción de equipo + +--- + +## Sección 4 — Vistas del usuario + +### Jugador registrado (`/panel-usuario`) + +Se agregan dos bloques al panel existente: + +**A. Mis pagos pendientes** +- Lista de cobros asignados al jugador: inscripción anual, sanciones +- Por cada uno: concepto, monto, estado, botón "Pagar ahora" +- Si ya está pagado: fecha + link al comprobante + +**B. Historial de pagos** +- Todos los pagos realizados (inscripciones, sanciones, compras en tienda) +- Filtro por estado y fecha + +### Tienda pública (`/tienda`) + +- Accesible desde la navbar para todos (registrados y anónimos) +- Catálogo: productos activos con stock > 0 +- Carrito en sesión (sin necesidad de cuenta) +- Checkout: formulario con nombre + email → pago con Macro +- Comprobante por email con información de retiro en sede +- Si el usuario tiene cuenta: la orden se vincula a su `user_id` y aparece en su historial + +### Invitación a registrarse (opcional, no obligatorio) +En el checkout anónimo se muestra: *"¿Tenés cuenta? Ingresá para guardar tu historial de compras."* + +--- + +## Sección 5 — Consideraciones técnicas y testing + +### Pendientes a confirmar con Macro al recibir credenciales + +- URL del endpoint para generar transacción / token de pago +- Formato exacto del webhook (campos, firma, algoritmo de verificación — probablemente HMAC-SHA256) +- Si existe **entorno sandbox** para pruebas (solicitarlo explícitamente) +- Si el botón es JS embebido, iframe, o redirect + +### Estructura de clases nuevas en Laravel + +``` +app/ + Services/ + MacroService.php ← toda la comunicación con Macro (stub hasta recibir credenciales) + PagoService.php ← crea pagos, dispara eventos post-pago + Events/ + PagoConfirmado.php + Listeners/ + EnviarComprobantePago.php + MarcarJugadorInscripto.php + MarcarSancionSaldada.php + ActualizarOrdenTienda.php + Jobs/ + CancelarPagosPendientesVencidos.php ← cron diario + Http/Controllers/ + CheckoutController.php + MacroWebhookController.php + TiendaController.php + Admin/ConceptoPagoController.php + Admin/SancionAdminController.php + Admin/ProductoController.php + Admin/OrdenTiendaController.php + Admin/RecaudacionController.php +``` + +### Variables de entorno necesarias + +```env +MACRO_MERCHANT_ID= +MACRO_API_KEY= +MACRO_SECRET= +MACRO_WEBHOOK_SECRET= +MACRO_ENV=sandbox # cambiar a "production" al salir a producción +MACRO_SUCCESS_URL="${APP_URL}/pagos/exitoso" +MACRO_FAILURE_URL="${APP_URL}/pagos/fallido" +MACRO_WEBHOOK_URL="${APP_URL}/api/macro/webhook" +``` + +### Plan de testing en dos etapas + +**Etapa 1 — Sin credenciales Macro (desarrollo inmediato)** +- Tests unitarios del modelo de datos: crear pagos, sanciones, órdenes +- Tests de eventos post-pago con `MacroService` mockeado +- Tests del webhook con payload simulado y firma fake +- Tests de las vistas admin (CRUD de conceptos, productos, sanciones) + +**Etapa 2 — Con Macro sandbox** +- Pruebas end-to-end con tarjeta de prueba en sandbox +- Verificar que el webhook llega y el estado se actualiza +- Verificar idempotencia (enviar webhook dos veces → mismo resultado) +- Verificar email de comprobante + +### Orden de implementación recomendado + +1. Migraciones + modelos (`ConceptoPago`, `Pago`, `Sancion`, `Producto`, `OrdenTienda`, `OrdenItem`) +2. `MacroService` con métodos stub +3. Panel admin: conceptos de pago → sanciones → tienda → reportes/recaudación +4. Panel usuario: pagos pendientes + historial +5. Tienda pública: catálogo + carrito + checkout +6. Webhook receptor + eventos post-pago + job de cancelación +7. Reemplazar stubs con implementación real de Macro al recibir credenciales + +--- + +## Inscripción de jugadores — detalle adicional ✅ + +### Dos tipos de inscripción por temporada + +La temporada abarca Torneo Apertura + Clausura. Hay jugadores que se incorporan a mitad de año y solo deben pagar una inscripción reducida. Por lo tanto, el super admin crea **dos conceptos** por temporada: + +| Concepto | Tipo | Ejemplo precio | Aplica a | +|---|---|---|---| +| Inscripción Anual Completa 2026 | `inscripcion_jugador` | $X | Jugadores desde Apertura | +| Inscripción Reducida 2026 (Clausura) | `inscripcion_jugador` | $Y | Jugadores que ingresan a mitad de año | + +### Generación masiva de deudas + +En el panel admin (super admin), sección **Conceptos de pago**, se agrega una acción: + +**"Generar deuda masiva"** +1. Super admin selecciona el concepto (ej: "Inscripción Anual Completa 2026") +2. Sistema muestra todos los jugadores activos que **no tienen ya ese concepto generado** para esa temporada +3. Super admin puede desmarcar jugadores individuales (ej: los que van a recibir la inscripción reducida) +4. Confirma → sistema crea un registro `pagos` (estado=pendiente) por cada jugador seleccionado +5. Cada jugador ve la deuda en su panel usuario + +Para los jugadores que se incorporan a mitad de año, el admin repite el proceso con el concepto "Inscripción Reducida". + +**Regla de negocio:** Un jugador no puede tener dos pagos pendientes del mismo concepto simultáneamente (se valida antes de generar). + +## Preguntas abiertas + +- ¿Tiene Macro un entorno sandbox para desarrollo? (pendiente confirmar con Diego Dallanora) +- ¿Qué campos exactos envía Macro en el webhook? (se confirma al recibir credenciales) diff --git a/lang/es/validation.php b/lang/es/validation.php new file mode 100644 index 0000000..03a0da3 --- /dev/null +++ b/lang/es/validation.php @@ -0,0 +1,173 @@ + 'El campo :attribute debe ser aceptado.', + 'accepted_if' => 'El campo :attribute debe ser aceptado cuando :other es :value.', + 'active_url' => 'El campo :attribute no es una URL válida.', + 'after' => 'El campo :attribute debe ser una fecha posterior a :date.', + 'after_or_equal' => 'El campo :attribute debe ser una fecha posterior o igual a :date.', + 'alpha' => 'El campo :attribute solo puede contener letras.', + 'alpha_dash' => 'El campo :attribute solo puede contener letras, números, guiones y guiones bajos.', + 'alpha_num' => 'El campo :attribute solo puede contener letras y números.', + 'array' => 'El campo :attribute debe ser un array.', + 'ascii' => 'El campo :attribute solo puede contener caracteres alfanuméricos de un byte y símbolos.', + 'before' => 'El campo :attribute debe ser una fecha anterior a :date.', + 'before_or_equal' => 'El campo :attribute debe ser una fecha anterior o igual a :date.', + 'between' => [ + 'array' => 'El campo :attribute debe tener entre :min y :max elementos.', + 'file' => 'El archivo :attribute debe pesar entre :min y :max kilobytes.', + 'numeric' => 'El campo :attribute debe estar entre :min y :max.', + 'string' => 'El campo :attribute debe tener entre :min y :max caracteres.', + ], + 'boolean' => 'El campo :attribute debe ser verdadero o falso.', + 'can' => 'El campo :attribute contiene un valor no autorizado.', + 'confirmed' => 'La confirmación de :attribute no coincide.', + 'current_password' => 'La contraseña es incorrecta.', + 'date' => 'El campo :attribute no es una fecha válida.', + 'date_equals' => 'El campo :attribute debe ser una fecha igual a :date.', + 'date_format' => 'El campo :attribute no corresponde con el formato :format.', + 'decimal' => 'El campo :attribute debe tener :decimal decimales.', + 'declined' => 'El campo :attribute debe ser rechazado.', + 'declined_if' => 'El campo :attribute debe ser rechazado cuando :other es :value.', + 'different' => 'El campo :attribute y :other deben ser diferentes.', + 'digits' => 'El campo :attribute debe tener :digits dígitos.', + 'digits_between' => 'El campo :attribute debe tener entre :min y :max dígitos.', + 'dimensions' => 'Las dimensiones de la imagen :attribute no son válidas.', + 'distinct' => 'El campo :attribute tiene un valor duplicado.', + 'doesnt_end_with' => 'El campo :attribute no debe terminar con uno de los siguientes valores: :values.', + 'doesnt_start_with' => 'El campo :attribute no debe comenzar con uno de los siguientes valores: :values.', + 'email' => 'El campo :attribute debe ser una dirección de correo válida.', + 'ends_with' => 'El campo :attribute debe terminar con uno de los siguientes valores: :values.', + 'enum' => 'El valor seleccionado para :attribute no es válido.', + 'exists' => 'El campo :attribute seleccionado no existe.', + 'extensions' => 'El campo :attribute debe tener una de las extensiones: :values.', + 'file' => 'El campo :attribute debe ser un archivo.', + 'filled' => 'El campo :attribute no puede estar vacío.', + 'gt' => [ + 'array' => 'El campo :attribute debe tener más de :value elementos.', + 'file' => 'El archivo :attribute debe ser mayor a :value kilobytes.', + 'numeric' => 'El campo :attribute debe ser mayor que :value.', + 'string' => 'El campo :attribute debe tener más de :value caracteres.', + ], + 'gte' => [ + 'array' => 'El campo :attribute debe tener :value elementos o más.', + 'file' => 'El archivo :attribute debe ser mayor o igual a :value kilobytes.', + 'numeric' => 'El campo :attribute debe ser mayor o igual que :value.', + 'string' => 'El campo :attribute debe tener :value caracteres o más.', + ], + 'hex_color' => 'El campo :attribute debe ser un color hexadecimal válido.', + 'image' => 'El campo :attribute debe ser una imagen.', + 'in' => 'El valor seleccionado para :attribute no es válido.', + 'in_array' => 'El campo :attribute no existe en :other.', + 'integer' => 'El campo :attribute debe ser un número entero.', + 'ip' => 'El campo :attribute debe ser una dirección IP válida.', + 'ipv4' => 'El campo :attribute debe ser una dirección IPv4 válida.', + 'ipv6' => 'El campo :attribute debe ser una dirección IPv6 válida.', + 'json' => 'El campo :attribute debe ser una cadena JSON válida.', + 'list' => 'El campo :attribute debe ser una lista.', + 'lowercase' => 'El campo :attribute debe estar en minúsculas.', + 'lt' => [ + 'array' => 'El campo :attribute debe tener menos de :value elementos.', + 'file' => 'El archivo :attribute debe ser menor a :value kilobytes.', + 'numeric' => 'El campo :attribute debe ser menor que :value.', + 'string' => 'El campo :attribute debe tener menos de :value caracteres.', + ], + 'lte' => [ + 'array' => 'El campo :attribute no debe tener más de :value elementos.', + 'file' => 'El archivo :attribute debe ser menor o igual a :value kilobytes.', + 'numeric' => 'El campo :attribute debe ser menor o igual que :value.', + 'string' => 'El campo :attribute debe tener :value caracteres o menos.', + ], + 'mac_address' => 'El campo :attribute debe ser una dirección MAC válida.', + 'max' => [ + 'array' => 'El campo :attribute no puede tener más de :max elementos.', + 'file' => 'El archivo :attribute no puede ser mayor a :max kilobytes.', + 'numeric' => 'El campo :attribute no puede ser mayor a :max.', + 'string' => 'El campo :attribute no puede tener más de :max caracteres.', + ], + 'max_digits' => 'El campo :attribute no puede tener más de :max dígitos.', + 'mimes' => 'El campo :attribute debe ser un archivo de tipo: :values.', + 'mimetypes' => 'El campo :attribute debe ser un archivo de tipo: :values.', + 'min' => [ + 'array' => 'El campo :attribute debe tener al menos :min elementos.', + 'file' => 'El archivo :attribute debe pesar al menos :min kilobytes.', + 'numeric' => 'El campo :attribute debe ser al menos :min.', + 'string' => 'El campo :attribute debe tener al menos :min caracteres.', + ], + 'min_digits' => 'El campo :attribute debe tener al menos :min dígitos.', + 'missing' => 'El campo :attribute debe estar ausente.', + 'missing_if' => 'El campo :attribute debe estar ausente cuando :other es :value.', + 'missing_unless' => 'El campo :attribute debe estar ausente a menos que :other sea :value.', + 'missing_with' => 'El campo :attribute debe estar ausente cuando :values está presente.', + 'missing_with_all' => 'El campo :attribute debe estar ausente cuando :values están presentes.', + 'multiple_of' => 'El campo :attribute debe ser un múltiplo de :value.', + 'not_in' => 'El valor seleccionado para :attribute no es válido.', + 'not_regex' => 'El formato del campo :attribute no es válido.', + 'numeric' => 'El campo :attribute debe ser un número.', + 'password' => [ + 'letters' => 'El campo :attribute debe contener al menos una letra.', + 'mixed' => 'El campo :attribute debe contener al menos una letra mayúscula y una minúscula.', + 'numbers' => 'El campo :attribute debe contener al menos un número.', + 'symbols' => 'El campo :attribute debe contener al menos un símbolo.', + 'uncompromised' => 'El :attribute dado ha aparecido en una filtración de datos. Por favor, elija un :attribute diferente.', + ], + 'present' => 'El campo :attribute debe estar presente.', + 'present_if' => 'El campo :attribute debe estar presente cuando :other es :value.', + 'present_unless' => 'El campo :attribute debe estar presente a menos que :other sea :value.', + 'present_with' => 'El campo :attribute debe estar presente cuando :values esté presente.', + 'present_with_all' => 'El campo :attribute debe estar presente cuando :values estén presentes.', + 'prohibited' => 'El campo :attribute está prohibido.', + 'prohibited_if' => 'El campo :attribute está prohibido cuando :other es :value.', + 'prohibited_unless' => 'El campo :attribute está prohibido a menos que :other esté en :values.', + 'prohibits' => 'El campo :attribute prohíbe que :other esté presente.', + 'regex' => 'El formato del campo :attribute no es válido.', + 'required' => 'El campo :attribute es obligatorio.', + 'required_array_keys' => 'El campo :attribute debe contener entradas para: :values.', + 'required_if' => 'El campo :attribute es obligatorio cuando :other es :value.', + 'required_if_accepted' => 'El campo :attribute es obligatorio cuando :other es aceptado.', + 'required_unless' => 'El campo :attribute es obligatorio a menos que :other esté en :values.', + 'required_with' => 'El campo :attribute es obligatorio cuando :values está presente.', + 'required_with_all' => 'El campo :attribute es obligatorio cuando :values están presentes.', + 'required_without' => 'El campo :attribute es obligatorio cuando :values no está presente.', + 'required_without_all' => 'El campo :attribute es obligatorio cuando ninguno de los valores :values está presente.', + 'same' => 'El campo :attribute y :other deben coincidir.', + 'size' => [ + 'array' => 'El campo :attribute debe contener :size elementos.', + 'file' => 'El archivo :attribute debe pesar :size kilobytes.', + 'numeric' => 'El campo :attribute debe ser :size.', + 'string' => 'El campo :attribute debe tener :size caracteres.', + ], + 'starts_with' => 'El campo :attribute debe comenzar con uno de los siguientes valores: :values.', + 'string' => 'El campo :attribute debe ser una cadena de texto.', + 'timezone' => 'El campo :attribute debe ser una zona horaria válida.', + 'unique' => 'El valor del campo :attribute ya está en uso.', + 'uploaded' => 'El archivo :attribute no pudo ser subido.', + 'uppercase' => 'El campo :attribute debe estar en mayúsculas.', + 'url' => 'El campo :attribute debe ser una URL válida.', + 'ulid' => 'El campo :attribute debe ser un ULID válido.', + 'uuid' => 'El campo :attribute debe ser un UUID válido.', + + 'attributes' => [ + 'nombre' => 'nombre', + 'imagen' => 'imagen', + 'url' => 'URL', + 'activo' => 'estado activo', + 'orden' => 'orden', + 'titulo' => 'título', + 'subtitulo' => 'subtítulo', + 'boton_texto' => 'texto del botón', + 'boton_enlace' => 'enlace del botón', + 'email' => 'correo electrónico', + 'password' => 'contraseña', + 'nombre_evento' => 'nombre del evento', + 'fecha_evento' => 'fecha del evento', + 'hora_inicio' => 'hora de inicio', + 'hora_fin' => 'hora de fin', + 'sede' => 'sede', + 'documento' => 'documento', + 'apellido' => 'apellido', + 'fecha_nacimiento' => 'fecha de nacimiento', + ], + +]; diff --git a/lang/vendor/backup/ar/notifications.php b/lang/vendor/backup/ar/notifications.php new file mode 100644 index 0000000..48bc709 --- /dev/null +++ b/lang/vendor/backup/ar/notifications.php @@ -0,0 +1,45 @@ + 'رسالة استثناء: :message', + 'exception_trace' => 'تتبع الإستثناء: :trace', + 'exception_message_title' => 'رسالة استثناء', + 'exception_trace_title' => 'تتبع الإستثناء', + + 'backup_failed_subject' => 'أخفق النسخ الاحتياطي لل :application_name', + 'backup_failed_body' => 'مهم: حدث خطأ أثناء النسخ الاحتياطي :application_name', + + 'backup_successful_subject' => 'نسخ احتياطي جديد ناجح ل :application_name', + 'backup_successful_subject_title' => 'نجاح النسخ الاحتياطي الجديد!', + 'backup_successful_body' => 'أخبار عظيمة، نسخة احتياطية جديدة ل :application_name تم إنشاؤها بنجاح على القرص المسمى :disk_name.', + + 'cleanup_failed_subject' => 'فشل تنظيف النسخ الاحتياطي للتطبيق :application_name .', + 'cleanup_failed_body' => 'حدث خطأ أثناء تنظيف النسخ الاحتياطية ل :application_name', + + 'cleanup_successful_subject' => 'تنظيف النسخ الاحتياطية ل :application_name تمت بنجاح', + 'cleanup_successful_subject_title' => 'تنظيف النسخ الاحتياطية تم بنجاح!', + 'cleanup_successful_body' => 'تنظيف النسخ الاحتياطية ل :application_name على القرص المسمى :disk_name تم بنجاح.', + + 'healthy_backup_found_subject' => 'النسخ الاحتياطية ل :application_name على القرص :disk_name صحية', + 'healthy_backup_found_subject_title' => 'النسخ الاحتياطية ل :application_name صحية', + 'healthy_backup_found_body' => 'تعتبر النسخ الاحتياطية ل :application_name صحية. عمل جيد!', + + 'unhealthy_backup_found_subject' => 'مهم: النسخ الاحتياطية ل :application_name غير صحية', + 'unhealthy_backup_found_subject_title' => 'مهم: النسخ الاحتياطية ل :application_name غير صحية. :problem', + 'unhealthy_backup_found_body' => 'النسخ الاحتياطية ل :application_name على القرص :disk_name غير صحية.', + 'unhealthy_backup_found_not_reachable' => 'لا يمكن الوصول إلى وجهة النسخ الاحتياطي. :error', + 'unhealthy_backup_found_empty' => 'لا توجد نسخ احتياطية لهذا التطبيق على الإطلاق.', + 'unhealthy_backup_found_old' => 'تم إنشاء أحدث النسخ الاحتياطية في :date وتعتبر قديمة جدا.', + 'unhealthy_backup_found_unknown' => 'عذرا، لا يمكن تحديد سبب دقيق.', + 'unhealthy_backup_found_full' => 'النسخ الاحتياطية تستخدم الكثير من التخزين. الاستخدام الحالي هو :disk_usage وهو أعلى من الحد المسموح به من :disk_limit.', + + 'no_backups_info' => 'لم يتم عمل نسخ احتياطية حتى الآن', + 'application_name' => 'اسم التطبيق', + 'backup_name' => 'اسم النسخ الاحتياطي', + 'disk' => 'القرص', + 'newest_backup_size' => 'أحدث حجم للنسخ الاحتياطي', + 'number_of_backups' => 'عدد النسخ الاحتياطية', + 'total_storage_used' => 'إجمالي مساحة التخزين المستخدمة', + 'newest_backup_date' => 'أحدث تاريخ النسخ الاحتياطي', + 'oldest_backup_date' => 'أقدم تاريخ نسخ احتياطي', +]; diff --git a/lang/vendor/backup/bg/notifications.php b/lang/vendor/backup/bg/notifications.php new file mode 100644 index 0000000..7c87d5b --- /dev/null +++ b/lang/vendor/backup/bg/notifications.php @@ -0,0 +1,45 @@ + 'Съобщение за изключение: :message', + 'exception_trace' => 'Проследяване на изключение: :trace', + 'exception_message_title' => 'Съобщение за изключение', + 'exception_trace_title' => 'Проследяване на изключение', + + 'backup_failed_subject' => 'Неуспешно резервно копие на :application_name', + 'backup_failed_body' => 'Важно: Възникна грешка при архивиране на :application_name', + + 'backup_successful_subject' => 'Успешно ново резервно копие на :application_name', + 'backup_successful_subject_title' => 'Успешно ново резервно копие!', + 'backup_successful_body' => 'Чудесни новини, ново резервно копие на :application_name беше успешно създадено на диска с име :disk_name.', + + 'cleanup_failed_subject' => 'Почистването на резервните копия на :application_name не бе успешно.', + 'cleanup_failed_body' => 'Възникна грешка при почистването на резервните копия на :application_name', + + 'cleanup_successful_subject' => 'Почистването на архивите на :application_name е успешно', + 'cleanup_successful_subject_title' => 'Почистването на резервните копия е успешно!', + 'cleanup_successful_body' => 'Почистването на резервни копия на :application_name на диска с име :disk_name беше успешно.', + + 'healthy_backup_found_subject' => 'Резервните копия за :application_name на диск :disk_name са здрави', + 'healthy_backup_found_subject_title' => 'Резервните копия за :application_name са здрави', + 'healthy_backup_found_body' => 'Резервните копия за :application_name се считат за здрави. Добра работа!', + + 'unhealthy_backup_found_subject' => 'Важно: Резервните копия за :application_name не са здрави', + 'unhealthy_backup_found_subject_title' => 'Важно: Резервните копия за :application_name не са здрави. :проблем', + 'unhealthy_backup_found_body' => 'Резервните копия за :application_name на диск :disk_name не са здрави.', + 'unhealthy_backup_found_not_reachable' => 'Дестинацията за резервни копия не може да бъде достигната. :грешка', + 'unhealthy_backup_found_empty' => 'Изобщо няма резервни копия на това приложение.', + 'unhealthy_backup_found_old' => 'Последното резервно копие, направено на :date, се счита за твърде старо.', + 'unhealthy_backup_found_unknown' => 'За съжаление не може да се определи точна причина.', + 'unhealthy_backup_found_full' => 'Резервните копия използват твърде много място за съхранение. Текущото използване е :disk_usage, което е по-високо от разрешеното ограничение на :disk_limit.', + + 'no_backups_info' => 'Все още не са правени резервни копия', + 'application_name' => 'Име на приложението', + 'backup_name' => 'Име на резервно копие', + 'disk' => 'Диск', + 'newest_backup_size' => 'Най-новият размер на резервно копие', + 'number_of_backups' => 'Брой резервни копия', + 'total_storage_used' => 'Общо използвано дисково пространство', + 'newest_backup_date' => 'Най-нова дата на резервно копие', + 'oldest_backup_date' => 'Най-старата дата на резервно копие', +]; diff --git a/lang/vendor/backup/bn/notifications.php b/lang/vendor/backup/bn/notifications.php new file mode 100644 index 0000000..bd0bf81 --- /dev/null +++ b/lang/vendor/backup/bn/notifications.php @@ -0,0 +1,45 @@ + 'এক্সসেপশন বার্তা: :message', + 'exception_trace' => 'এক্সসেপশন ট্রেস: :trace', + 'exception_message_title' => 'এক্সসেপশন message', + 'exception_trace_title' => 'এক্সসেপশন ট্রেস', + + 'backup_failed_subject' => ':application_name এর ব্যাকআপ ব্যর্থ হয়েছে।', + 'backup_failed_body' => 'গুরুত্বপূর্ণঃ :application_name ব্যাক আপ করার সময় একটি ত্রুটি ঘটেছে।', + + 'backup_successful_subject' => ':application_name এর নতুন ব্যাকআপ সফল হয়েছে।', + 'backup_successful_subject_title' => 'নতুন ব্যাকআপ সফল হয়েছে!', + 'backup_successful_body' => 'খুশির খবর, :application_name এর নতুন ব্যাকআপ :disk_name ডিস্কে সফলভাবে তৈরি হয়েছে।', + + 'cleanup_failed_subject' => ':application_name ব্যাকআপগুলি সাফ করতে ব্যর্থ হয়েছে।', + 'cleanup_failed_body' => ':application_name ব্যাকআপগুলি সাফ করার সময় একটি ত্রুটি ঘটেছে।', + + 'cleanup_successful_subject' => ':application_name এর ব্যাকআপগুলি সফলভাবে সাফ করা হয়েছে।', + 'cleanup_successful_subject_title' => 'ব্যাকআপগুলি সফলভাবে সাফ করা হয়েছে!', + 'cleanup_successful_body' => ':application_name এর ব্যাকআপগুলি :disk_name ডিস্ক থেকে সফলভাবে সাফ করা হয়েছে।', + + 'healthy_backup_found_subject' => ':application_name এর ব্যাকআপগুলি :disk_name ডিস্কে স্বাস্থ্যকর অবস্থায় আছে।', + 'healthy_backup_found_subject_title' => ':application_name এর ব্যাকআপগুলি স্বাস্থ্যকর অবস্থায় আছে।', + 'healthy_backup_found_body' => ':application_name এর ব্যাকআপগুলি স্বাস্থ্যকর বিবেচনা করা হচ্ছে। Good job!', + + 'unhealthy_backup_found_subject' => 'গুরুত্বপূর্ণঃ :application_name এর ব্যাকআপগুলি অস্বাস্থ্যকর অবস্থায় আছে।', + 'unhealthy_backup_found_subject_title' => 'গুরুত্বপূর্ণঃ :application_name এর ব্যাকআপগুলি অস্বাস্থ্যকর অবস্থায় আছে। :problem', + 'unhealthy_backup_found_body' => ':disk_name ডিস্কের :application_name এর ব্যাকআপগুলি অস্বাস্থ্যকর অবস্থায় আছে।', + 'unhealthy_backup_found_not_reachable' => 'ব্যাকআপ গন্তব্যে পৌঁছানো যায় নি। :error', + 'unhealthy_backup_found_empty' => 'এই অ্যাপ্লিকেশনটির কোনও ব্যাকআপ নেই।', + 'unhealthy_backup_found_old' => 'সর্বশেষ ব্যাকআপ যেটি :date এই তারিখে করা হয়েছে, সেটি খুব পুরানো।', + 'unhealthy_backup_found_unknown' => 'দুঃখিত, সঠিক কারণ নির্ধারণ করা সম্ভব হয়নি।', + 'unhealthy_backup_found_full' => 'ব্যাকআপগুলি অতিরিক্ত স্টোরেজ ব্যবহার করছে। বর্তমান ব্যবহারের পরিমান :disk_usage যা অনুমোদিত সীমা :disk_limit এর বেশি।', + + 'no_backups_info' => 'কোনো ব্যাকআপ এখনও তৈরি হয়নি', + 'application_name' => 'আবেদনের নাম', + 'backup_name' => 'ব্যাকআপের নাম', + 'disk' => 'ডিস্ক', + 'newest_backup_size' => 'নতুন ব্যাকআপ আকার', + 'number_of_backups' => 'ব্যাকআপের সংখ্যা', + 'total_storage_used' => 'ব্যবহৃত মোট সঞ্চয়স্থান', + 'newest_backup_date' => 'নতুন ব্যাকআপের তারিখ', + 'oldest_backup_date' => 'পুরানো ব্যাকআপের তারিখ', +]; diff --git a/lang/vendor/backup/cs/notifications.php b/lang/vendor/backup/cs/notifications.php new file mode 100644 index 0000000..9a145d9 --- /dev/null +++ b/lang/vendor/backup/cs/notifications.php @@ -0,0 +1,45 @@ + 'Zpráva výjimky: :message', + 'exception_trace' => 'Stopa výjimky: :trace', + 'exception_message_title' => 'Zpráva výjimky', + 'exception_trace_title' => 'Stopa výjimky', + + 'backup_failed_subject' => 'Záloha :application_name neuspěla', + 'backup_failed_body' => 'Důležité: Při záloze :application_name se vyskytla chyba', + + 'backup_successful_subject' => 'Úspěšná nová záloha :application_name', + 'backup_successful_subject_title' => 'Úspěšná nová záloha!', + 'backup_successful_body' => 'Dobrá zpráva, na disku jménem :disk_name byla úspěšně vytvořena nová záloha :application_name.', + + 'cleanup_failed_subject' => 'Vyčištění záloh :application_name neuspělo.', + 'cleanup_failed_body' => 'Při čištění záloh :application_name se vyskytla chyba', + + 'cleanup_successful_subject' => 'Vyčištění záloh :application_name úspěšné', + 'cleanup_successful_subject_title' => 'Vyčištění záloh bylo úspěšné!', + 'cleanup_successful_body' => 'Vyčištění záloh :application_name na disku jménem :disk_name bylo úspěšné.', + + 'healthy_backup_found_subject' => 'Zálohy pro :application_name na disku :disk_name jsou zdravé', + 'healthy_backup_found_subject_title' => 'Zálohy pro :application_name jsou zdravé', + 'healthy_backup_found_body' => 'Zálohy pro :application_name jsou považovány za zdravé. Dobrá práce!', + + 'unhealthy_backup_found_subject' => 'Důležité: Zálohy pro :application_name jsou nezdravé', + 'unhealthy_backup_found_subject_title' => 'Důležité: Zálohy pro :application_name jsou nezdravé. :problem', + 'unhealthy_backup_found_body' => 'Zálohy pro :application_name na disku :disk_name jsou nezdravé.', + 'unhealthy_backup_found_not_reachable' => 'Nelze se dostat k cíli zálohy. :error', + 'unhealthy_backup_found_empty' => 'Tato aplikace nemá vůbec žádné zálohy.', + 'unhealthy_backup_found_old' => 'Poslední záloha vytvořená dne :date je považována za příliš starou.', + 'unhealthy_backup_found_unknown' => 'Omlouváme se, nemůžeme určit přesný důvod.', + 'unhealthy_backup_found_full' => 'Zálohy zabírají příliš mnoho místa na disku. Aktuální využití disku je :disk_usage, což je vyšší než povolený limit :disk_limit.', + + 'no_backups_info' => 'Zatím nebyly vytvořeny žádné zálohy', + 'application_name' => 'Název aplikace', + 'backup_name' => 'Název zálohy', + 'disk' => 'Disk', + 'newest_backup_size' => 'Velikost nejnovější zálohy', + 'number_of_backups' => 'Počet záloh', + 'total_storage_used' => 'Celková využitá kapacita úložiště', + 'newest_backup_date' => 'Datum nejnovější zálohy', + 'oldest_backup_date' => 'Datum nejstarší zálohy', +]; diff --git a/lang/vendor/backup/da/notifications.php b/lang/vendor/backup/da/notifications.php new file mode 100644 index 0000000..80b4697 --- /dev/null +++ b/lang/vendor/backup/da/notifications.php @@ -0,0 +1,45 @@ + 'Fejlbesked: :message', + 'exception_trace' => 'Fejl trace: :trace', + 'exception_message_title' => 'Fejlbesked', + 'exception_trace_title' => 'Fejl trace', + + 'backup_failed_subject' => 'Backup af :application_name fejlede', + 'backup_failed_body' => 'Vigtigt: Der skete en fejl under backup af :application_name', + + 'backup_successful_subject' => 'Ny backup af :application_name oprettet', + 'backup_successful_subject_title' => 'Ny backup!', + 'backup_successful_body' => 'Gode nyheder - der blev oprettet en ny backup af :application_name på disken :disk_name.', + + 'cleanup_failed_subject' => 'Oprydning af backups for :application_name fejlede.', + 'cleanup_failed_body' => 'Der skete en fejl under oprydning af backups for :application_name', + + 'cleanup_successful_subject' => 'Oprydning af backups for :application_name gennemført', + 'cleanup_successful_subject_title' => 'Backup oprydning gennemført!', + 'cleanup_successful_body' => 'Oprydningen af backups for :application_name på disken :disk_name er gennemført.', + + 'healthy_backup_found_subject' => 'Alle backups for :application_name på disken :disk_name er OK', + 'healthy_backup_found_subject_title' => 'Alle backups for :application_name er OK', + 'healthy_backup_found_body' => 'Alle backups for :application_name er ok. Godt gået!', + + 'unhealthy_backup_found_subject' => 'Vigtigt: Backups for :application_name fejlbehæftede', + 'unhealthy_backup_found_subject_title' => 'Vigtigt: Backups for :application_name er fejlbehæftede. :problem', + 'unhealthy_backup_found_body' => 'Backups for :application_name på disken :disk_name er fejlbehæftede.', + 'unhealthy_backup_found_not_reachable' => 'Backup destinationen kunne ikke findes. :error', + 'unhealthy_backup_found_empty' => 'Denne applikation har ingen backups overhovedet.', + 'unhealthy_backup_found_old' => 'Den seneste backup fra :date er for gammel.', + 'unhealthy_backup_found_unknown' => 'Beklager, en præcis årsag kunne ikke findes.', + 'unhealthy_backup_found_full' => 'Backups bruger for meget plads. Nuværende disk forbrug er :disk_usage, hvilket er mere end den tilladte grænse på :disk_limit.', + + 'no_backups_info' => 'Der blev ikke foretaget nogen sikkerhedskopier endnu', + 'application_name' => 'Applikationens navn', + 'backup_name' => 'Backup navn', + 'disk' => 'Disk', + 'newest_backup_size' => 'Nyeste backup-størrelse', + 'number_of_backups' => 'Antal sikkerhedskopier', + 'total_storage_used' => 'Samlet lagerplads brugt', + 'newest_backup_date' => 'Nyeste backup-størrelse', + 'oldest_backup_date' => 'Ældste backup-størrelse', +]; diff --git a/lang/vendor/backup/de/notifications.php b/lang/vendor/backup/de/notifications.php new file mode 100644 index 0000000..acce789 --- /dev/null +++ b/lang/vendor/backup/de/notifications.php @@ -0,0 +1,45 @@ + 'Fehlermeldung: :message', + 'exception_trace' => 'Fehlerverfolgung: :trace', + 'exception_message_title' => 'Fehlermeldung', + 'exception_trace_title' => 'Fehlerverfolgung', + + 'backup_failed_subject' => 'Backup von :application_name konnte nicht erstellt werden', + 'backup_failed_body' => 'Wichtig: Beim Backup von :application_name ist ein Fehler aufgetreten', + + 'backup_successful_subject' => 'Erfolgreiches neues Backup von :application_name', + 'backup_successful_subject_title' => 'Erfolgreiches neues Backup!', + 'backup_successful_body' => 'Gute Nachrichten, ein neues Backup von :application_name wurde erfolgreich erstellt und in :disk_name gepeichert.', + + 'cleanup_failed_subject' => 'Aufräumen der Backups von :application_name schlug fehl.', + 'cleanup_failed_body' => 'Beim aufräumen der Backups von :application_name ist ein Fehler aufgetreten', + + 'cleanup_successful_subject' => 'Aufräumen der Backups von :application_name backups erfolgreich', + 'cleanup_successful_subject_title' => 'Aufräumen der Backups erfolgreich!', + 'cleanup_successful_body' => 'Aufräumen der Backups von :application_name in :disk_name war erfolgreich.', + + 'healthy_backup_found_subject' => 'Die Backups von :application_name in :disk_name sind gesund', + 'healthy_backup_found_subject_title' => 'Die Backups von :application_name sind Gesund', + 'healthy_backup_found_body' => 'Die Backups von :application_name wurden als gesund eingestuft. Gute Arbeit!', + + 'unhealthy_backup_found_subject' => 'Wichtig: Die Backups für :application_name sind nicht gesund', + 'unhealthy_backup_found_subject_title' => 'Wichtig: Die Backups für :application_name sind ungesund. :problem', + 'unhealthy_backup_found_body' => 'Die Backups für :application_name in :disk_name sind ungesund.', + 'unhealthy_backup_found_not_reachable' => 'Das Backup Ziel konnte nicht erreicht werden. :error', + 'unhealthy_backup_found_empty' => 'Es gibt für die Anwendung noch gar keine Backups.', + 'unhealthy_backup_found_old' => 'Das letzte Backup am :date ist zu lange her.', + 'unhealthy_backup_found_unknown' => 'Sorry, ein genauer Grund konnte nicht gefunden werden.', + 'unhealthy_backup_found_full' => 'Die Backups verbrauchen zu viel Platz. Aktuell wird :disk_usage belegt, dass ist höher als das erlaubte Limit von :disk_limit.', + + 'no_backups_info' => 'Bisher keine Backups vorhanden', + 'application_name' => 'Applikationsname', + 'backup_name' => 'Backup Name', + 'disk' => 'Speicherort', + 'newest_backup_size' => 'Neuste Backup-Größe', + 'number_of_backups' => 'Anzahl Backups', + 'total_storage_used' => 'Gesamter genutzter Speicherplatz', + 'newest_backup_date' => 'Neustes Backup', + 'oldest_backup_date' => 'Ältestes Backup', +]; diff --git a/lang/vendor/backup/en/notifications.php b/lang/vendor/backup/en/notifications.php new file mode 100644 index 0000000..73811bd --- /dev/null +++ b/lang/vendor/backup/en/notifications.php @@ -0,0 +1,45 @@ + 'Exception message: :message', + 'exception_trace' => 'Exception trace: :trace', + 'exception_message_title' => 'Exception message', + 'exception_trace_title' => 'Exception trace', + + 'backup_failed_subject' => 'Failed backup of :application_name', + 'backup_failed_body' => 'Important: An error occurred while backing up :application_name', + + 'backup_successful_subject' => 'Successful new backup of :application_name', + 'backup_successful_subject_title' => 'Successful new backup!', + 'backup_successful_body' => 'Great news, a new backup of :application_name was successfully created on the disk named :disk_name.', + + 'cleanup_failed_subject' => 'Cleaning up the backups of :application_name failed.', + 'cleanup_failed_body' => 'An error occurred while cleaning up the backups of :application_name', + + 'cleanup_successful_subject' => 'Clean up of :application_name backups successful', + 'cleanup_successful_subject_title' => 'Clean up of backups successful!', + 'cleanup_successful_body' => 'The clean up of the :application_name backups on the disk named :disk_name was successful.', + + 'healthy_backup_found_subject' => 'The backups for :application_name on disk :disk_name are healthy', + 'healthy_backup_found_subject_title' => 'The backups for :application_name are healthy', + 'healthy_backup_found_body' => 'The backups for :application_name are considered healthy. Good job!', + + 'unhealthy_backup_found_subject' => 'Important: The backups for :application_name are unhealthy', + 'unhealthy_backup_found_subject_title' => 'Important: The backups for :application_name are unhealthy. :problem', + 'unhealthy_backup_found_body' => 'The backups for :application_name on disk :disk_name are unhealthy.', + 'unhealthy_backup_found_not_reachable' => 'The backup destination cannot be reached. :error', + 'unhealthy_backup_found_empty' => 'There are no backups of this application at all.', + 'unhealthy_backup_found_old' => 'The latest backup made on :date is considered too old.', + 'unhealthy_backup_found_unknown' => 'Sorry, an exact reason cannot be determined.', + 'unhealthy_backup_found_full' => 'The backups are using too much storage. Current usage is :disk_usage which is higher than the allowed limit of :disk_limit.', + + 'no_backups_info' => 'No backups were made yet', + 'application_name' => 'Application name', + 'backup_name' => 'Backup name', + 'disk' => 'Disk', + 'newest_backup_size' => 'Newest backup size', + 'number_of_backups' => 'Number of backups', + 'total_storage_used' => 'Total storage used', + 'newest_backup_date' => 'Newest backup date', + 'oldest_backup_date' => 'Oldest backup date', +]; diff --git a/lang/vendor/backup/es/notifications.php b/lang/vendor/backup/es/notifications.php new file mode 100644 index 0000000..d2071b8 --- /dev/null +++ b/lang/vendor/backup/es/notifications.php @@ -0,0 +1,45 @@ + 'Mensaje de la excepción: :message', + 'exception_trace' => 'Traza de la excepción: :trace', + 'exception_message_title' => 'Mensaje de la excepción', + 'exception_trace_title' => 'Traza de la excepción', + + 'backup_failed_subject' => 'Copia de seguridad de :application_name fallida', + 'backup_failed_body' => 'Importante: Ocurrió un error al realizar la copia de seguridad de :application_name', + + 'backup_successful_subject' => 'Se completó con éxito la copia de seguridad de :application_name', + 'backup_successful_subject_title' => '¡Nueva copia de seguridad creada con éxito!', + 'backup_successful_body' => 'Buenas noticias, una nueva copia de seguridad de :application_name fue creada con éxito en el disco llamado :disk_name.', + + 'cleanup_failed_subject' => 'La limpieza de copias de seguridad de :application_name falló.', + 'cleanup_failed_body' => 'Ocurrió un error mientras se realizaba la limpieza de copias de seguridad de :application_name', + + 'cleanup_successful_subject' => 'La limpieza de copias de seguridad de :application_name se completó con éxito', + 'cleanup_successful_subject_title' => '!Limpieza de copias de seguridad completada con éxito!', + 'cleanup_successful_body' => 'La limpieza de copias de seguridad de :application_name en el disco llamado :disk_name se completo con éxito.', + + 'healthy_backup_found_subject' => 'Las copias de seguridad de :application_name en el disco :disk_name están en buen estado', + 'healthy_backup_found_subject_title' => 'Las copias de seguridad de :application_name están en buen estado', + 'healthy_backup_found_body' => 'Las copias de seguridad de :application_name se consideran en buen estado. ¡Buen trabajo!', + + 'unhealthy_backup_found_subject' => 'Importante: Las copias de seguridad de :application_name están en mal estado', + 'unhealthy_backup_found_subject_title' => 'Importante: Las copias de seguridad de :application_name están en mal estado. :problem', + 'unhealthy_backup_found_body' => 'Las copias de seguridad de :application_name en el disco :disk_name están en mal estado.', + 'unhealthy_backup_found_not_reachable' => 'No se puede acceder al destino de la copia de seguridad. :error', + 'unhealthy_backup_found_empty' => 'No existe ninguna copia de seguridad de esta aplicación.', + 'unhealthy_backup_found_old' => 'La última copia de seguriad hecha en :date es demasiado antigua.', + 'unhealthy_backup_found_unknown' => 'Lo siento, no es posible determinar la razón exacta.', + 'unhealthy_backup_found_full' => 'Las copias de seguridad están ocupando demasiado espacio. El espacio utilizado actualmente es :disk_usage el cual es mayor que el límite permitido de :disk_limit.', + + 'no_backups_info' => 'Aún no se hicieron copias de seguridad', + 'application_name' => 'Nombre de la aplicación', + 'backup_name' => 'Nombre de la copia de seguridad', + 'disk' => 'Disco', + 'newest_backup_size' => 'Tamaño de copia de seguridad más reciente', + 'number_of_backups' => 'Número de copias de seguridad', + 'total_storage_used' => 'Almacenamiento total utilizado', + 'newest_backup_date' => 'Fecha de la copia de seguridad más reciente', + 'oldest_backup_date' => 'Fecha de la copia de seguridad más antigua', +]; diff --git a/lang/vendor/backup/fa/notifications.php b/lang/vendor/backup/fa/notifications.php new file mode 100644 index 0000000..580a1f1 --- /dev/null +++ b/lang/vendor/backup/fa/notifications.php @@ -0,0 +1,45 @@ + 'پیغام خطا: :message', + 'exception_trace' => 'جزییات خطا: :trace', + 'exception_message_title' => 'پیغام خطا', + 'exception_trace_title' => 'جزییات خطا', + + 'backup_failed_subject' => 'پشتیبان‌گیری :application_name با خطا مواجه شد.', + 'backup_failed_body' => 'پیغام مهم: هنگام پشتیبان‌گیری از :application_name خطایی رخ داده است. ', + + 'backup_successful_subject' => 'نسخه پشتیبان جدید :application_name با موفقیت ساخته شد.', + 'backup_successful_subject_title' => 'پشتیبان‌گیری موفق!', + 'backup_successful_body' => 'خبر خوب، به تازگی نسخه پشتیبان :application_name روی دیسک :disk_name با موفقیت ساخته شد. ', + + 'cleanup_failed_subject' => 'پاک‌‌سازی نسخه پشتیبان :application_name انجام نشد.', + 'cleanup_failed_body' => 'هنگام پاک‌سازی نسخه پشتیبان :application_name خطایی رخ داده است.', + + 'cleanup_successful_subject' => 'پاک‌سازی نسخه پشتیبان :application_name با موفقیت انجام شد.', + 'cleanup_successful_subject_title' => 'پاک‌سازی نسخه پشتیبان!', + 'cleanup_successful_body' => 'پاک‌سازی نسخه پشتیبان :application_name روی دیسک :disk_name با موفقیت انجام شد.', + + 'healthy_backup_found_subject' => 'نسخه پشتیبان :application_name روی دیسک :disk_name سالم بود.', + 'healthy_backup_found_subject_title' => 'نسخه پشتیبان :application_name سالم بود.', + 'healthy_backup_found_body' => 'نسخه پشتیبان :application_name به نظر سالم میاد. دمت گرم!', + + 'unhealthy_backup_found_subject' => 'خبر مهم: نسخه پشتیبان :application_name سالم نبود.', + 'unhealthy_backup_found_subject_title' => 'خبر مهم: نسخه پشتیبان :application_name سالم نبود. :problem', + 'unhealthy_backup_found_body' => 'نسخه پشتیبان :application_name روی دیسک :disk_name سالم نبود.', + 'unhealthy_backup_found_not_reachable' => 'مقصد پشتیبان‌گیری در دسترس نبود. :error', + 'unhealthy_backup_found_empty' => 'برای این برنامه هیچ نسخه پشتیبانی وجود ندارد.', + 'unhealthy_backup_found_old' => 'آخرین نسخه پشتیبان برای تاریخ :date است، که به نظر خیلی قدیمی میاد. ', + 'unhealthy_backup_found_unknown' => 'متاسفانه دلیل دقیقی قابل تعیین نیست.', + 'unhealthy_backup_found_full' => 'نسخه‌های پشتیبان حجم زیادی اشغال کرده‌اند. میزان دیسک استفاده‌شده :disk_usage است که از میزان مجاز :disk_limit فراتر رفته است. ', + + 'no_backups_info' => 'هنوز نسخه پشتیبان تهیه نشده است', + 'application_name' => 'نام نرم‌افزار', + 'backup_name' => 'نام نسخه پشتیبان', + 'disk' => 'دیسک', + 'newest_backup_size' => 'اندازه جدیدترین نسخه پشتیبان', + 'number_of_backups' => 'تعداد نسخه‌های پشتیبان', + 'total_storage_used' => 'کل فضای ذخیره‌سازی استفاده‌شده', + 'newest_backup_date' => 'تاریخ جدیدترین نسخه پشتیبان', + 'oldest_backup_date' => 'تاریخ قدیمی‌ترین نسخه پشتیبان', +]; diff --git a/lang/vendor/backup/fi/notifications.php b/lang/vendor/backup/fi/notifications.php new file mode 100644 index 0000000..98bec62 --- /dev/null +++ b/lang/vendor/backup/fi/notifications.php @@ -0,0 +1,45 @@ + 'Virheilmoitus: :message', + 'exception_trace' => 'Virhe, jäljitys: :trace', + 'exception_message_title' => 'Virheilmoitus', + 'exception_trace_title' => 'Virheen jäljitys', + + 'backup_failed_subject' => ':application_name varmuuskopiointi epäonnistui', + 'backup_failed_body' => 'HUOM!: :application_name varmuuskoipionnissa tapahtui virhe', + + 'backup_successful_subject' => ':application_name varmuuskopioitu onnistuneesti', + 'backup_successful_subject_title' => 'Uusi varmuuskopio!', + 'backup_successful_body' => 'Hyviä uutisia! :application_name on varmuuskopioitu levylle :disk_name.', + + 'cleanup_failed_subject' => ':application_name varmuuskopioiden poistaminen epäonnistui.', + 'cleanup_failed_body' => ':application_name varmuuskopioiden poistamisessa tapahtui virhe.', + + 'cleanup_successful_subject' => ':application_name varmuuskopiot poistettu onnistuneesti', + 'cleanup_successful_subject_title' => 'Varmuuskopiot poistettu onnistuneesti!', + 'cleanup_successful_body' => ':application_name varmuuskopiot poistettu onnistuneesti levyltä :disk_name.', + + 'healthy_backup_found_subject' => ':application_name varmuuskopiot levyllä :disk_name ovat kunnossa', + 'healthy_backup_found_subject_title' => ':application_name varmuuskopiot ovat kunnossa', + 'healthy_backup_found_body' => ':application_name varmuuskopiot ovat kunnossa. Hieno homma!', + + 'unhealthy_backup_found_subject' => 'HUOM!: :application_name varmuuskopiot ovat vialliset', + 'unhealthy_backup_found_subject_title' => 'HUOM!: :application_name varmuuskopiot ovat vialliset. :problem', + 'unhealthy_backup_found_body' => ':application_name varmuuskopiot levyllä :disk_name ovat vialliset.', + 'unhealthy_backup_found_not_reachable' => 'Varmuuskopioiden kohdekansio ei ole saatavilla. :error', + 'unhealthy_backup_found_empty' => 'Tästä sovelluksesta ei ole varmuuskopioita.', + 'unhealthy_backup_found_old' => 'Viimeisin varmuuskopio, luotu :date, on liian vanha.', + 'unhealthy_backup_found_unknown' => 'Virhe, tarkempaa tietoa syystä ei valitettavasti ole saatavilla.', + 'unhealthy_backup_found_full' => 'Varmuuskopiot vievät liikaa levytilaa. Tällä hetkellä käytössä :disk_usage, mikä on suurempi kuin sallittu tilavuus (:disk_limit).', + + 'no_backups_info' => 'Varmuuskopioita ei vielä tehty', + 'application_name' => 'Sovelluksen nimi', + 'backup_name' => 'Varmuuskopion nimi', + 'disk' => 'Levy', + 'newest_backup_size' => 'Uusin varmuuskopion koko', + 'number_of_backups' => 'Varmuuskopioiden määrä', + 'total_storage_used' => 'Käytetty tallennustila yhteensä', + 'newest_backup_date' => 'Uusin varmuuskopion koko', + 'oldest_backup_date' => 'Vanhin varmuuskopion koko', +]; diff --git a/lang/vendor/backup/fr/notifications.php b/lang/vendor/backup/fr/notifications.php new file mode 100644 index 0000000..ad60a5c --- /dev/null +++ b/lang/vendor/backup/fr/notifications.php @@ -0,0 +1,45 @@ + "Message de l'exception : :message", + 'exception_trace' => "Trace de l'exception : :trace", + 'exception_message_title' => "Message de l'exception", + 'exception_trace_title' => "Trace de l'exception", + + 'backup_failed_subject' => 'Échec de la sauvegarde de :application_name', + 'backup_failed_body' => 'Important : Une erreur est survenue lors de la sauvegarde de :application_name', + + 'backup_successful_subject' => 'Succès de la sauvegarde de :application_name', + 'backup_successful_subject_title' => 'Sauvegarde créée avec succès !', + 'backup_successful_body' => 'Bonne nouvelle, une nouvelle sauvegarde de :application_name a été créée avec succès sur le disque nommé :disk_name.', + + 'cleanup_failed_subject' => 'Le nettoyage des sauvegardes de :application_name a echoué.', + 'cleanup_failed_body' => 'Une erreur est survenue lors du nettoyage des sauvegardes de :application_name', + + 'cleanup_successful_subject' => 'Succès du nettoyage des sauvegardes de :application_name', + 'cleanup_successful_subject_title' => 'Sauvegardes nettoyées avec succès !', + 'cleanup_successful_body' => 'Le nettoyage des sauvegardes de :application_name sur le disque nommé :disk_name a été effectué avec succès.', + + 'healthy_backup_found_subject' => 'Les sauvegardes pour :application_name sur le disque :disk_name sont saines', + 'healthy_backup_found_subject_title' => 'Les sauvegardes pour :application_name sont saines', + 'healthy_backup_found_body' => 'Les sauvegardes pour :application_name sont considérées saines. Bon travail !', + + 'unhealthy_backup_found_subject' => 'Important : Les sauvegardes pour :application_name sont corrompues', + 'unhealthy_backup_found_subject_title' => 'Important : Les sauvegardes pour :application_name sont corrompues. :problem', + 'unhealthy_backup_found_body' => 'Les sauvegardes pour :application_name sur le disque :disk_name sont corrompues.', + 'unhealthy_backup_found_not_reachable' => "La destination de la sauvegarde n'est pas accessible. :error", + 'unhealthy_backup_found_empty' => "Il n'y a aucune sauvegarde pour cette application.", + 'unhealthy_backup_found_old' => 'La dernière sauvegarde du :date est considérée trop vieille.', + 'unhealthy_backup_found_unknown' => 'Désolé, une raison exacte ne peut être déterminée.', + 'unhealthy_backup_found_full' => 'Les sauvegardes utilisent trop d\'espace disque. L\'utilisation actuelle est de :disk_usage alors que la limite autorisée est de :disk_limit.', + + 'no_backups_info' => 'Aucune sauvegarde n\'a encore été effectuée', + 'application_name' => "Nom de l'application", + 'backup_name' => 'Nom de la sauvegarde', + 'disk' => 'Disque', + 'newest_backup_size' => 'Taille de la sauvegarde la plus récente', + 'number_of_backups' => 'Nombre de sauvegardes', + 'total_storage_used' => 'Stockage total utilisé', + 'newest_backup_date' => 'Date de la sauvegarde la plus récente', + 'oldest_backup_date' => 'Date de la sauvegarde la plus ancienne', +]; diff --git a/lang/vendor/backup/he/notifications.php b/lang/vendor/backup/he/notifications.php new file mode 100644 index 0000000..db3b35f --- /dev/null +++ b/lang/vendor/backup/he/notifications.php @@ -0,0 +1,45 @@ + 'הודעת חריגה: :message', + 'exception_trace' => 'מעקב חריגה: :trace', + 'exception_message_title' => 'הודעת חריגה', + 'exception_trace_title' => 'מעקב חריגה', + + 'backup_failed_subject' => 'כשל בגיבוי של :application_name', + 'backup_failed_body' => 'חשוב: אירעה שגיאה במהלך גיבוי היישום :application_name', + + 'backup_successful_subject' => 'גיבוי חדש מוצלח של :application_name', + 'backup_successful_subject_title' => 'גיבוי חדש מוצלח!', + 'backup_successful_body' => 'חדשות טובות, גיבוי חדש של :application_name נוצר בהצלחה על הדיסק בשם :disk_name.', + + 'cleanup_failed_subject' => 'נכשל בניקוי הגיבויים של :application_name', + 'cleanup_failed_body' => 'אירעה שגיאה במהלך ניקוי הגיבויים של :application_name', + + 'cleanup_successful_subject' => 'ניקוי הגיבויים של :application_name בוצע בהצלחה', + 'cleanup_successful_subject_title' => 'ניקוי הגיבויים בוצע בהצלחה!', + 'cleanup_successful_body' => 'ניקוי הגיבויים של :application_name על הדיסק בשם :disk_name בוצע בהצלחה.', + + 'healthy_backup_found_subject' => 'הגיבויים של :application_name על הדיסק :disk_name תקינים', + 'healthy_backup_found_subject_title' => 'הגיבויים של :application_name תקינים', + 'healthy_backup_found_body' => 'הגיבויים של :application_name נחשבים לתקינים. עבודה טובה!', + + 'unhealthy_backup_found_subject' => 'חשוב: הגיבויים של :application_name אינם תקינים', + 'unhealthy_backup_found_subject_title' => 'חשוב: הגיבויים של :application_name אינם תקינים. :problem', + 'unhealthy_backup_found_body' => 'הגיבויים של :application_name על הדיסק :disk_name אינם תקינים.', + 'unhealthy_backup_found_not_reachable' => 'לא ניתן להגיע ליעד הגיבוי. :error', + 'unhealthy_backup_found_empty' => 'אין גיבויים של היישום הזה בכלל.', + 'unhealthy_backup_found_old' => 'הגיבוי האחרון שנעשה בתאריך :date נחשב כישן מדי.', + 'unhealthy_backup_found_unknown' => 'מצטערים, לא ניתן לקבוע סיבה מדויקת.', + 'unhealthy_backup_found_full' => 'הגיבויים משתמשים בשטח אחסון רב מידי. שימוש הנוכחי הוא :disk_usage, שגבול המותר הוא :disk_limit.', + + 'no_backups_info' => 'לא נעשו עדיין גיבויים', + 'application_name' => 'שם היישום', + 'backup_name' => 'שם הגיבוי', + 'disk' => 'דיסק', + 'newest_backup_size' => 'גודל הגיבוי החדש ביותר', + 'number_of_backups' => 'מספר הגיבויים', + 'total_storage_used' => 'סך האחסון המופעל', + 'newest_backup_date' => 'תאריך הגיבוי החדש ביותר', + 'oldest_backup_date' => 'תאריך הגיבוי הישן ביותר', +]; diff --git a/lang/vendor/backup/hi/notifications.php b/lang/vendor/backup/hi/notifications.php new file mode 100644 index 0000000..f812867 --- /dev/null +++ b/lang/vendor/backup/hi/notifications.php @@ -0,0 +1,45 @@ + 'अपवाद संदेश: :message', + 'exception_trace' => 'अपवाद निशान: :trace', + 'exception_message_title' => 'अपवादी संदेश', + 'exception_trace_title' => 'अपवाद निशान', + + 'backup_failed_subject' => ':application_name का बैकअप असफल रहा', + 'backup_failed_body' => 'जरूरी सुचना: :application_name का बैकअप लेते समय असफल रहे', + + 'backup_successful_subject' => ':application_name का बैकअप सफल रहा', + 'backup_successful_subject_title' => 'बैकअप सफल रहा!', + 'backup_successful_body' => 'खुशखबर, :application_name का बैकअप :disk_name पर संग्रहित करने मे सफल रहे.', + + 'cleanup_failed_subject' => ':application_name के बैकअप की सफाई असफल रही.', + 'cleanup_failed_body' => ':application_name के बैकअप की सफाई करते समय कुछ बाधा आयी है.', + + 'cleanup_successful_subject' => ':application_name के बैकअप की सफाई सफल रही', + 'cleanup_successful_subject_title' => 'बैकअप की सफाई सफल रही!', + 'cleanup_successful_body' => ':application_name का बैकअप जो :disk_name नाम की डिस्क पर संग्रहित है, उसकी सफाई सफल रही.', + + 'healthy_backup_found_subject' => ':disk_name नाम की डिस्क पर संग्रहित :application_name के बैकअप स्वस्थ है', + 'healthy_backup_found_subject_title' => ':application_name के सभी बैकअप स्वस्थ है', + 'healthy_backup_found_body' => 'बहुत बढ़िया! :application_name के सभी बैकअप स्वस्थ है.', + + 'unhealthy_backup_found_subject' => 'जरूरी सुचना : :application_name के बैकअप अस्वस्थ है', + 'unhealthy_backup_found_subject_title' => 'जरूरी सुचना : :application_name के बैकअप :problem के बजेसे अस्वस्थ है', + 'unhealthy_backup_found_body' => ':disk_name नाम की डिस्क पर संग्रहित :application_name के बैकअप अस्वस्थ है', + 'unhealthy_backup_found_not_reachable' => ':error के बजेसे बैकअप की मंजिल तक पोहोच नहीं सकते.', + 'unhealthy_backup_found_empty' => 'इस एप्लीकेशन का कोई भी बैकअप नहीं है.', + 'unhealthy_backup_found_old' => 'हालहीमें :date को लिया हुआ बैकअप बहुत पुराना है.', + 'unhealthy_backup_found_unknown' => 'माफ़ कीजिये, सही कारण निर्धारित नहीं कर सकते.', + 'unhealthy_backup_found_full' => 'सभी बैकअप बहुत ज्यादा जगह का उपयोग कर रहे है. फ़िलहाल सभी बैकअप :disk_usage जगह का उपयोग कर रहे है, जो की :disk_limit अनुमति सीमा से अधिक का है.', + + 'no_backups_info' => 'अभी तक कोई बैकअप नहीं बनाया गया था', + 'application_name' => 'आवेदन का नाम', + 'backup_name' => 'बैकअप नाम', + 'disk' => 'डिस्क', + 'newest_backup_size' => 'नवीनतम बैकअप आकार', + 'number_of_backups' => 'बैकअप की संख्या', + 'total_storage_used' => 'उपयोग किया गया कुल संग्रहण', + 'newest_backup_date' => 'नवीनतम बैकअप आकार', + 'oldest_backup_date' => 'सबसे पुराना बैकअप आकार', +]; diff --git a/lang/vendor/backup/hr/notifications.php b/lang/vendor/backup/hr/notifications.php new file mode 100644 index 0000000..0b12bfd --- /dev/null +++ b/lang/vendor/backup/hr/notifications.php @@ -0,0 +1,45 @@ + 'Greška: :message', + 'exception_trace' => 'Praćenje greške: :trace', + 'exception_message_title' => 'Greška', + 'exception_trace_title' => 'Praćenje greške', + + 'backup_failed_subject' => 'Neuspješno sigurnosno kopiranje za :application_name', + 'backup_failed_body' => 'Važno: Došlo je do greške prilikom sigurnosnog kopiranja za :application_name', + + 'backup_successful_subject' => 'Uspješno sigurnosno kopiranje za :application_name', + 'backup_successful_subject_title' => 'Uspješno sigurnosno kopiranje!', + 'backup_successful_body' => 'Nova sigurnosna kopija za :application_name je uspješno spremljena na disk :disk_name.', + + 'cleanup_failed_subject' => 'Neuspješno čišćenje sigurnosnih kopija za :application_name', + 'cleanup_failed_body' => 'Došlo je do greške prilikom čišćenja sigurnosnih kopija za :application_name', + + 'cleanup_successful_subject' => 'Uspješno čišćenje sigurnosnih kopija za :application_name', + 'cleanup_successful_subject_title' => 'Uspješno čišćenje sigurnosnih kopija!', + 'cleanup_successful_body' => 'Sigurnosne kopije za :application_name su uspješno očišćene s diska :disk_name.', + + 'healthy_backup_found_subject' => 'Sigurnosne kopije za :application_name na disku :disk_name su zdrave', + 'healthy_backup_found_subject_title' => 'Sigurnosne kopije za :application_name su zdrave', + 'healthy_backup_found_body' => 'Sigurnosne kopije za :application_name se smatraju zdravima. Svaka čast!', + + 'unhealthy_backup_found_subject' => 'Važno: Sigurnosne kopije za :application_name su nezdrave', + 'unhealthy_backup_found_subject_title' => 'Važno: Sigurnosne kopije za :application_name su nezdrave. :problem', + 'unhealthy_backup_found_body' => 'Sigurnosne kopije za :application_name na disku :disk_name su nezdrave.', + 'unhealthy_backup_found_not_reachable' => 'Destinacija sigurnosne kopije nije dohvatljiva. :error', + 'unhealthy_backup_found_empty' => 'Nijedna sigurnosna kopija ove aplikacije ne postoji.', + 'unhealthy_backup_found_old' => 'Zadnja sigurnosna kopija generirana na datum :date smatra se prestarom.', + 'unhealthy_backup_found_unknown' => 'Isprike, ali nije moguće odrediti razlog.', + 'unhealthy_backup_found_full' => 'Sigurnosne kopije zauzimaju previše prostora. Trenutno zauzeće je :disk_usage što je više od dozvoljenog ograničenja od :disk_limit.', + + 'no_backups_info' => 'Nema sigurnosnih kopija', + 'application_name' => 'Naziv aplikacije', + 'backup_name' => 'Naziv sigurnosne kopije', + 'disk' => 'Disk', + 'newest_backup_size' => 'Veličina najnovije sigurnosne kopije', + 'number_of_backups' => 'Broj sigurnosnih kopija', + 'total_storage_used' => 'Ukupno zauzeće', + 'newest_backup_date' => 'Najnovija kopija na datum', + 'oldest_backup_date' => 'Najstarija kopija na datum', +]; diff --git a/lang/vendor/backup/id/notifications.php b/lang/vendor/backup/id/notifications.php new file mode 100644 index 0000000..12364b5 --- /dev/null +++ b/lang/vendor/backup/id/notifications.php @@ -0,0 +1,45 @@ + 'Pesan pengecualian: :message', + 'exception_trace' => 'Jejak pengecualian: :trace', + 'exception_message_title' => 'Pesan pengecualian', + 'exception_trace_title' => 'Jejak pengecualian', + + 'backup_failed_subject' => 'Gagal backup :application_name', + 'backup_failed_body' => 'Penting: Sebuah error terjadi ketika membackup :application_name', + + 'backup_successful_subject' => 'Backup baru sukses dari :application_name', + 'backup_successful_subject_title' => 'Backup baru sukses!', + 'backup_successful_body' => 'Kabar baik, sebuah backup baru dari :application_name sukses dibuat pada disk bernama :disk_name.', + + 'cleanup_failed_subject' => 'Membersihkan backup dari :application_name yang gagal.', + 'cleanup_failed_body' => 'Sebuah error teradi ketika membersihkan backup dari :application_name', + + 'cleanup_successful_subject' => 'Sukses membersihkan backup :application_name', + 'cleanup_successful_subject_title' => 'Sukses membersihkan backup!', + 'cleanup_successful_body' => 'Pembersihan backup :application_name pada disk bernama :disk_name telah sukses.', + + 'healthy_backup_found_subject' => 'Backup untuk :application_name pada disk :disk_name sehat', + 'healthy_backup_found_subject_title' => 'Backup untuk :application_name sehat', + 'healthy_backup_found_body' => 'Backup untuk :application_name dipertimbangkan sehat. Kerja bagus!', + + 'unhealthy_backup_found_subject' => 'Penting: Backup untuk :application_name tidak sehat', + 'unhealthy_backup_found_subject_title' => 'Penting: Backup untuk :application_name tidak sehat. :problem', + 'unhealthy_backup_found_body' => 'Backup untuk :application_name pada disk :disk_name tidak sehat.', + 'unhealthy_backup_found_not_reachable' => 'Tujuan backup tidak dapat terjangkau. :error', + 'unhealthy_backup_found_empty' => 'Tidak ada backup pada aplikasi ini sama sekali.', + 'unhealthy_backup_found_old' => 'Backup terakhir dibuat pada :date dimana dipertimbahkan sudah sangat lama.', + 'unhealthy_backup_found_unknown' => 'Maaf, sebuah alasan persisnya tidak dapat ditentukan.', + 'unhealthy_backup_found_full' => 'Backup menggunakan terlalu banyak kapasitas penyimpanan. Penggunaan terkini adalah :disk_usage dimana lebih besar dari batas yang diperbolehkan yaitu :disk_limit.', + + 'no_backups_info' => 'Belum ada backup yang dibuat', + 'application_name' => 'Nama aplikasi', + 'backup_name' => 'Nama cadangan', + 'disk' => 'Disk', + 'newest_backup_size' => 'Ukuran cadangan terbaru', + 'number_of_backups' => 'Jumlah cadangan', + 'total_storage_used' => 'Total penyimpanan yang digunakan', + 'newest_backup_date' => 'Ukuran cadangan terbaru', + 'oldest_backup_date' => 'Ukuran cadangan tertua', +]; diff --git a/lang/vendor/backup/it/notifications.php b/lang/vendor/backup/it/notifications.php new file mode 100644 index 0000000..e96618d --- /dev/null +++ b/lang/vendor/backup/it/notifications.php @@ -0,0 +1,45 @@ + "Messaggio dell'eccezione: :message", + 'exception_trace' => "Traccia dell'eccezione: :trace", + 'exception_message_title' => "Messaggio dell'eccezione", + 'exception_trace_title' => "Traccia dell'eccezione", + + 'backup_failed_subject' => 'Fallito il backup di :application_name', + 'backup_failed_body' => 'Importante: Si è verificato un errore durante il backup di :application_name', + + 'backup_successful_subject' => 'Creato nuovo backup di :application_name', + 'backup_successful_subject_title' => 'Nuovo backup creato!', + 'backup_successful_body' => 'Grande notizia, un nuovo backup di :application_name è stato creato con successo sul disco :disk_name.', + + 'cleanup_failed_subject' => 'Pulizia dei backup di :application_name fallita.', + 'cleanup_failed_body' => 'Si è verificato un errore durante la pulizia dei backup di :application_name', + + 'cleanup_successful_subject' => 'Pulizia dei backup di :application_name avvenuta con successo', + 'cleanup_successful_subject_title' => 'Pulizia dei backup avvenuta con successo!', + 'cleanup_successful_body' => 'La pulizia dei backup di :application_name sul disco :disk_name è avvenuta con successo.', + + 'healthy_backup_found_subject' => 'I backup per :application_name sul disco :disk_name sono sani', + 'healthy_backup_found_subject_title' => 'I backup per :application_name sono sani', + 'healthy_backup_found_body' => 'I backup per :application_name sono considerati sani. Bel Lavoro!', + + 'unhealthy_backup_found_subject' => 'Importante: i backup per :application_name sono corrotti', + 'unhealthy_backup_found_subject_title' => 'Importante: i backup per :application_name sono corrotti. :problem', + 'unhealthy_backup_found_body' => 'I backup per :application_name sul disco :disk_name sono corrotti.', + 'unhealthy_backup_found_not_reachable' => 'Impossibile raggiungere la destinazione di backup. :error', + 'unhealthy_backup_found_empty' => 'Non esiste alcun backup di questa applicazione.', + 'unhealthy_backup_found_old' => 'L\'ultimo backup fatto il :date è considerato troppo vecchio.', + 'unhealthy_backup_found_unknown' => 'Spiacenti, non è possibile determinare una ragione esatta.', + 'unhealthy_backup_found_full' => 'I backup utilizzano troppa memoria. L\'utilizzo corrente è :disk_usage che è superiore al limite consentito di :disk_limit.', + + 'no_backups_info' => 'Non sono stati ancora effettuati backup', + 'application_name' => "Nome dell'applicazione", + 'backup_name' => 'Nome di backup', + 'disk' => 'Disco', + 'newest_backup_size' => 'Dimensione backup più recente', + 'number_of_backups' => 'Numero di backup', + 'total_storage_used' => 'Spazio di archiviazione totale utilizzato', + 'newest_backup_date' => 'Data del backup più recente', + 'oldest_backup_date' => 'Data del backup più vecchio', +]; diff --git a/lang/vendor/backup/ja/notifications.php b/lang/vendor/backup/ja/notifications.php new file mode 100644 index 0000000..1b57ca3 --- /dev/null +++ b/lang/vendor/backup/ja/notifications.php @@ -0,0 +1,45 @@ + '例外のメッセージ: :message', + 'exception_trace' => '例外の追跡: :trace', + 'exception_message_title' => '例外のメッセージ', + 'exception_trace_title' => '例外の追跡', + + 'backup_failed_subject' => ':application_name のバックアップに失敗しました。', + 'backup_failed_body' => '重要: :application_name のバックアップ中にエラーが発生しました。', + + 'backup_successful_subject' => ':application_name のバックアップに成功しました。', + 'backup_successful_subject_title' => 'バックアップに成功しました!', + 'backup_successful_body' => '朗報です。ディスク :disk_name へ :application_name のバックアップが成功しました。', + + 'cleanup_failed_subject' => ':application_name のバックアップ削除に失敗しました。', + 'cleanup_failed_body' => ':application_name のバックアップ削除中にエラーが発生しました。', + + 'cleanup_successful_subject' => ':application_name のバックアップ削除に成功しました。', + 'cleanup_successful_subject_title' => 'バックアップ削除に成功しました!', + 'cleanup_successful_body' => 'ディスク :disk_name に保存された :application_name のバックアップ削除に成功しました。', + + 'healthy_backup_found_subject' => 'ディスク :disk_name への :application_name のバックアップは正常です。', + 'healthy_backup_found_subject_title' => ':application_name のバックアップは正常です。', + 'healthy_backup_found_body' => ':application_name へのバックアップは正常です。いい仕事してますね!', + + 'unhealthy_backup_found_subject' => '重要: :application_name のバックアップに異常があります。', + 'unhealthy_backup_found_subject_title' => '重要: :application_name のバックアップに異常があります。 :problem', + 'unhealthy_backup_found_body' => ':disk_name への :application_name のバックアップに異常があります。', + 'unhealthy_backup_found_not_reachable' => 'バックアップ先にアクセスできませんでした。 :error', + 'unhealthy_backup_found_empty' => 'このアプリケーションのバックアップは見つかりませんでした。', + 'unhealthy_backup_found_old' => ':date に保存された直近のバックアップが古すぎます。', + 'unhealthy_backup_found_unknown' => '申し訳ございません。予期せぬエラーです。', + 'unhealthy_backup_found_full' => 'バックアップがディスク容量を圧迫しています。現在の使用量 :disk_usage は、許可された限界値 :disk_limit を超えています。', + + 'no_backups_info' => 'バックアップはまだ作成されていません', + 'application_name' => 'アプリケーション名', + 'backup_name' => 'バックアップ名', + 'disk' => 'ディスク', + 'newest_backup_size' => '最新のバックアップサイズ', + 'number_of_backups' => 'バックアップ数', + 'total_storage_used' => '使用された合計ストレージ', + 'newest_backup_date' => '最新のバックアップ日時', + 'oldest_backup_date' => '最も古いバックアップ日時', +]; diff --git a/lang/vendor/backup/kk/notifications.php b/lang/vendor/backup/kk/notifications.php new file mode 100644 index 0000000..2382e48 --- /dev/null +++ b/lang/vendor/backup/kk/notifications.php @@ -0,0 +1,45 @@ + 'Қате туралы хабарлама: :message', + 'exception_trace' => 'Қате туралы мәліметтер: :trace', + 'exception_message_title' => 'Қате туралы хабарлама', + 'exception_trace_title' => 'Қате туралы мәліметтер', + + 'backup_failed_subject' => ':application_name бағдарламасының резервтік көшірмесін жасау сәтсіз аяқталды', + 'backup_failed_body' => 'Маңызды: :application_name бағдарламасының резервтік көшірмесін жасау барысында қате орын алды', + + 'backup_successful_subject' => ':application_name бағдарламасының жаңа резервтік көшірмесі сәтті құрылды', + 'backup_successful_subject_title' => 'Жаңа резервтік көшірме сәтті құрылды!', + 'backup_successful_body' => 'Жақсы жаңалық: :application_name бағдарламасының жаңа резервтік көшірмесі :disk_name дискінде сәтті құрылды.', + + 'cleanup_failed_subject' => ':application_name бағдарламасының резервтік көшірмелерін тазалау сәтсіз аяқталды', + 'cleanup_failed_body' => ':application_name бағдарламасының резервтік көшірмелерін тазалау барысында қате орын алды', + + 'cleanup_successful_subject' => ':application_name бағдарламасының резервтік көшірмелерін тазалау сәтті өтті', + 'cleanup_successful_subject_title' => 'Резервтік көшірмелерді тазалау сәтті аяқталды!', + 'cleanup_successful_body' => ':disk_name дискіндегі :application_name бағдарламасының резервтік көшірмелерін тазалау сәтті аяқталды.', + + 'healthy_backup_found_subject' => ':disk_name дискіндегі :application_name бағдарламасының резервтік көшірмелері қалыпты күйде', + 'healthy_backup_found_subject_title' => ':application_name бағдарламасының резервтік көшірмелері қалыпты күйде', + 'healthy_backup_found_body' => ':application_name бағдарламасының резервтік көшірмелері толық тексеруден өтті. Өте жақсы!', + + 'unhealthy_backup_found_subject' => 'Маңызды: :application_name бағдарламасының резервтік көшірмелері жарамсыз күйде', + 'unhealthy_backup_found_subject_title' => 'Маңызды: :application_name бағдарламасының резервтік көшірмелері жарамсыз күйде. :problem', + 'unhealthy_backup_found_body' => ':disk_name дискіндегі :application_name бағдарламасының резервтік көшірмелері жарамсыз күйде.', + 'unhealthy_backup_found_not_reachable' => 'Резервтік көшірме сақтау орнына қол жеткізу мүмкін емес. :error', + 'unhealthy_backup_found_empty' => 'Осы бағдарлама бойынша резервтік көшірмелер әлі жасалмаған.', + 'unhealthy_backup_found_old' => 'Соңғы резервтік көшірме (:date) тым ескі болып саналады.', + 'unhealthy_backup_found_unknown' => 'Кешіріңіз, нақты себебін анықтау мүмкін емес.', + 'unhealthy_backup_found_full' => 'Резервтік көшірмелер тым көп орын алып отыр. Ағымдағы пайдалану көлемі :disk_usage, бұл рұқсат етілген шектен :disk_limit аса жоғары.', + + 'no_backups_info' => 'Әлі резервтік көшірме жасалмаған', + 'application_name' => 'Бағдарлама атауы', + 'backup_name' => 'Резервтік көшірме атауы', + 'disk' => 'Диск', + 'newest_backup_size' => 'Соңғы резервтік көшірменің көлемі', + 'number_of_backups' => 'Резервтік көшірмелер саны', + 'total_storage_used' => 'Жалпы қолданылған сақтау көлемі', + 'newest_backup_date' => 'Соңғы резервтік көшірме күні', + 'oldest_backup_date' => 'Ең ескі резервтік көшірме күні', +]; diff --git a/lang/vendor/backup/ko/notifications.php b/lang/vendor/backup/ko/notifications.php new file mode 100644 index 0000000..d13c0f9 --- /dev/null +++ b/lang/vendor/backup/ko/notifications.php @@ -0,0 +1,45 @@ + '예외 메시지: :message', + 'exception_trace' => '예외 추적: :trace', + 'exception_message_title' => '예외 메시지', + 'exception_trace_title' => '예외 추적', + + 'backup_failed_subject' => ':application_name 백업 실패', + 'backup_failed_body' => '중요: :application_name 백업 중 오류 발생', + + 'backup_successful_subject' => ':application_name 백업 성공', + 'backup_successful_subject_title' => '백업이 성공적으로 완료되었습니다!', + 'backup_successful_body' => '좋은 소식입니다. :disk_name 디스크에 :application_name 백업이 성공적으로 완료되었습니다.', + + 'cleanup_failed_subject' => ':application_name 백업 정리 실패', + 'cleanup_failed_body' => ':application_name 백업 정리 중 오류 발생', + + 'cleanup_successful_subject' => ':application_name 백업 정리 성공', + 'cleanup_successful_subject_title' => '백업 정리가 성공적으로 완료되었습니다!', + 'cleanup_successful_body' => ':disk_name 디스크에 저장된 :application_name 백업 정리가 성공적으로 완료되었습니다.', + + 'healthy_backup_found_subject' => ':application_name 백업은 정상입니다.', + 'healthy_backup_found_subject_title' => ':application_name 백업은 정상입니다.', + 'healthy_backup_found_body' => ':application_name 백업은 정상입니다. 수고하셨습니다!', + + 'unhealthy_backup_found_subject' => '중요: :application_name 백업에 문제가 있습니다.', + 'unhealthy_backup_found_subject_title' => '중요: :application_name 백업에 문제가 있습니다. :problem', + 'unhealthy_backup_found_body' => ':disk_name 디스크에 :application_name 백업에 문제가 있습니다.', + 'unhealthy_backup_found_not_reachable' => '백업 위치에 액세스할 수 없습니다. :error', + 'unhealthy_backup_found_empty' => '이 애플리케이션에는 백업이 없습니다.', + 'unhealthy_backup_found_old' => ':date에 저장된 최신 백업이 너무 오래되었습니다.', + 'unhealthy_backup_found_unknown' => '죄송합니다. 예기치 않은 오류가 발생했습니다.', + 'unhealthy_backup_found_full' => '백업이 디스크 공간을 다 차지하고 있습니다. 현재 사용량 :disk_usage는 허용 한도 :disk_limit을 초과합니다.', + + 'no_backups_info' => '아직 백업이 생성되지 않았습니다.', + 'application_name' => '애플리케이션 이름', + 'backup_name' => '백업 이름', + 'disk' => '디스크', + 'newest_backup_size' => '최신 백업 크기', + 'number_of_backups' => '백업 수', + 'total_storage_used' => '총 사용 스토리지', + 'newest_backup_date' => '최신 백업 날짜', + 'oldest_backup_date' => '가장 오래된 백업 날짜', +]; diff --git a/lang/vendor/backup/nl/notifications.php b/lang/vendor/backup/nl/notifications.php new file mode 100644 index 0000000..4887cbf --- /dev/null +++ b/lang/vendor/backup/nl/notifications.php @@ -0,0 +1,45 @@ + 'Fout bericht: :message', + 'exception_trace' => 'Fout trace: :trace', + 'exception_message_title' => 'Fout bericht', + 'exception_trace_title' => 'Fout trace', + + 'backup_failed_subject' => 'Back-up van :application_name mislukt', + 'backup_failed_body' => 'Belangrijk: Er ging iets fout tijdens het maken van een back-up van :application_name', + + 'backup_successful_subject' => 'Succesvolle nieuwe back-up van :application_name', + 'backup_successful_subject_title' => 'Succesvolle nieuwe back-up!', + 'backup_successful_body' => 'Goed nieuws, een nieuwe back-up van :application_name was succesvol aangemaakt op de schijf genaamd :disk_name.', + + 'cleanup_failed_subject' => 'Het opschonen van de back-ups van :application_name is mislukt.', + 'cleanup_failed_body' => 'Er ging iets fout tijdens het opschonen van de back-ups van :application_name', + + 'cleanup_successful_subject' => 'Opschonen van :application_name back-ups was succesvol.', + 'cleanup_successful_subject_title' => 'Opschonen van back-ups was succesvol!', + 'cleanup_successful_body' => 'Het opschonen van de :application_name back-ups op de schijf genaamd :disk_name was succesvol.', + + 'healthy_backup_found_subject' => 'De back-ups voor :application_name op schijf :disk_name zijn gezond', + 'healthy_backup_found_subject_title' => 'De back-ups voor :application_name zijn gezond', + 'healthy_backup_found_body' => 'De back-ups voor :application_name worden als gezond beschouwd. Goed gedaan!', + + 'unhealthy_backup_found_subject' => 'Belangrijk: De back-ups voor :application_name zijn niet meer gezond', + 'unhealthy_backup_found_subject_title' => 'Belangrijk: De back-ups voor :application_name zijn niet gezond. :problem', + 'unhealthy_backup_found_body' => 'De back-ups voor :application_name op schijf :disk_name zijn niet gezond.', + 'unhealthy_backup_found_not_reachable' => 'De back-upbestemming kon niet worden bereikt. :error', + 'unhealthy_backup_found_empty' => 'Er zijn geen back-ups van deze applicatie beschikbaar.', + 'unhealthy_backup_found_old' => 'De laatste back-up gemaakt op :date is te oud.', + 'unhealthy_backup_found_unknown' => 'Sorry, een exacte reden kon niet worden bepaald.', + 'unhealthy_backup_found_full' => 'De back-ups gebruiken te veel opslagruimte. Momenteel wordt er :disk_usage gebruikt wat hoger is dan de toegestane limiet van :disk_limit.', + + 'no_backups_info' => 'Er zijn nog geen back-ups gemaakt', + 'application_name' => 'Naam van de toepassing', + 'backup_name' => 'Back-upnaam', + 'disk' => 'Schijf', + 'newest_backup_size' => 'Nieuwste back-upgrootte', + 'number_of_backups' => 'Aantal back-ups', + 'total_storage_used' => 'Totale gebruikte opslagruimte', + 'newest_backup_date' => 'Datum nieuwste back-up', + 'oldest_backup_date' => 'Datum oudste back-up', +]; diff --git a/lang/vendor/backup/no/notifications.php b/lang/vendor/backup/no/notifications.php new file mode 100644 index 0000000..e1d7019 --- /dev/null +++ b/lang/vendor/backup/no/notifications.php @@ -0,0 +1,45 @@ + 'Exception: :message', + 'exception_trace' => 'Exception trace: :trace', + 'exception_message_title' => 'Exception', + 'exception_trace_title' => 'Exception trace', + + 'backup_failed_subject' => 'Backup feilet for :application_name', + 'backup_failed_body' => 'Viktg: En feil oppstod under backing av :application_name', + + 'backup_successful_subject' => 'Gjennomført backup av :application_name', + 'backup_successful_subject_title' => 'Gjennomført backup!', + 'backup_successful_body' => 'Gode nyheter, en ny backup av :application_name ble opprettet på disken :disk_name.', + + 'cleanup_failed_subject' => 'Opprydding av backup for :application_name feilet.', + 'cleanup_failed_body' => 'En feil oppstod under opprydding av backups for :application_name', + + 'cleanup_successful_subject' => 'Opprydding av backup for :application_name gjennomført', + 'cleanup_successful_subject_title' => 'Opprydding av backup gjennomført!', + 'cleanup_successful_body' => 'Oppryddingen av backup for :application_name på disken :disk_name har blitt gjennomført.', + + 'healthy_backup_found_subject' => 'Alle backups for :application_name på disken :disk_name er OK', + 'healthy_backup_found_subject_title' => 'Alle backups for :application_name er OK', + 'healthy_backup_found_body' => 'Alle backups for :application_name er ok. Godt jobba!', + + 'unhealthy_backup_found_subject' => 'Viktig: Backups for :application_name ikke OK', + 'unhealthy_backup_found_subject_title' => 'Viktig: Backups for :application_name er ikke OK. :problem', + 'unhealthy_backup_found_body' => 'Backups for :application_name på disken :disk_name er ikke OK.', + 'unhealthy_backup_found_not_reachable' => 'Kunne ikke finne backup-destinasjonen. :error', + 'unhealthy_backup_found_empty' => 'Denne applikasjonen mangler backups.', + 'unhealthy_backup_found_old' => 'Den siste backupem fra :date er for gammel.', + 'unhealthy_backup_found_unknown' => 'Beklager, kunne ikke finne nøyaktig årsak.', + 'unhealthy_backup_found_full' => 'Backups bruker for mye lagringsplass. Nåværende diskbruk er :disk_usage, som er mer enn den tillatte grensen på :disk_limit.', + + 'no_backups_info' => 'Ingen sikkerhetskopier ble gjort ennå', + 'application_name' => 'Programnavn', + 'backup_name' => 'Navn på sikkerhetskopi', + 'disk' => 'Disk', + 'newest_backup_size' => 'Nyeste backup-størrelse', + 'number_of_backups' => 'Antall sikkerhetskopier', + 'total_storage_used' => 'Total lagring brukt', + 'newest_backup_date' => 'Nyeste backup-størrelse', + 'oldest_backup_date' => 'Eldste sikkerhetskopistørrelse', +]; diff --git a/lang/vendor/backup/pl/notifications.php b/lang/vendor/backup/pl/notifications.php new file mode 100644 index 0000000..5e79902 --- /dev/null +++ b/lang/vendor/backup/pl/notifications.php @@ -0,0 +1,45 @@ + 'Błąd: :message', + 'exception_trace' => 'Zrzut błędu: :trace', + 'exception_message_title' => 'Błąd', + 'exception_trace_title' => 'Zrzut błędu', + + 'backup_failed_subject' => 'Tworzenie kopii zapasowej aplikacji :application_name nie powiodło się', + 'backup_failed_body' => 'Ważne: Wystąpił błąd podczas tworzenia kopii zapasowej aplikacji :application_name', + + 'backup_successful_subject' => 'Pomyślnie utworzono kopię zapasową aplikacji :application_name', + 'backup_successful_subject_title' => 'Nowa kopia zapasowa!', + 'backup_successful_body' => 'Wspaniała wiadomość, nowa kopia zapasowa aplikacji :application_name została pomyślnie utworzona na dysku o nazwie :disk_name.', + + 'cleanup_failed_subject' => 'Czyszczenie kopii zapasowych aplikacji :application_name nie powiodło się.', + 'cleanup_failed_body' => 'Wystąpił błąd podczas czyszczenia kopii zapasowej aplikacji :application_name', + + 'cleanup_successful_subject' => 'Kopie zapasowe aplikacji :application_name zostały pomyślnie wyczyszczone', + 'cleanup_successful_subject_title' => 'Kopie zapasowe zostały pomyślnie wyczyszczone!', + 'cleanup_successful_body' => 'Czyszczenie kopii zapasowych aplikacji :application_name na dysku :disk_name zakończone sukcesem.', + + 'healthy_backup_found_subject' => 'Kopie zapasowe aplikacji :application_name na dysku :disk_name są poprawne', + 'healthy_backup_found_subject_title' => 'Kopie zapasowe aplikacji :application_name są poprawne', + 'healthy_backup_found_body' => 'Kopie zapasowe aplikacji :application_name są poprawne. Dobra robota!', + + 'unhealthy_backup_found_subject' => 'Ważne: Kopie zapasowe aplikacji :application_name są niepoprawne', + 'unhealthy_backup_found_subject_title' => 'Ważne: Kopie zapasowe aplikacji :application_name są niepoprawne. :problem', + 'unhealthy_backup_found_body' => 'Kopie zapasowe aplikacji :application_name na dysku :disk_name są niepoprawne.', + 'unhealthy_backup_found_not_reachable' => 'Miejsce docelowe kopii zapasowej nie jest osiągalne. :error', + 'unhealthy_backup_found_empty' => 'W aplikacji nie ma żadnej kopii zapasowych tej aplikacji.', + 'unhealthy_backup_found_old' => 'Ostatnia kopia zapasowa wykonania dnia :date jest zbyt stara.', + 'unhealthy_backup_found_unknown' => 'Niestety, nie można ustalić dokładnego błędu.', + 'unhealthy_backup_found_full' => 'Kopie zapasowe zajmują zbyt dużo miejsca. Obecne użycie dysku :disk_usage jest większe od ustalonego limitu :disk_limit.', + + 'no_backups_info' => 'Nie utworzono jeszcze kopii zapasowych', + 'application_name' => 'Nazwa aplikacji', + 'backup_name' => 'Nazwa kopii zapasowej', + 'disk' => 'Dysk', + 'newest_backup_size' => 'Najnowszy rozmiar kopii zapasowej', + 'number_of_backups' => 'Liczba kopii zapasowych', + 'total_storage_used' => 'Całkowite wykorzystane miejsce', + 'newest_backup_date' => 'Najnowszy rozmiar kopii zapasowej', + 'oldest_backup_date' => 'Najstarszy rozmiar kopii zapasowej', +]; diff --git a/lang/vendor/backup/pt/notifications.php b/lang/vendor/backup/pt/notifications.php new file mode 100644 index 0000000..835cfeb --- /dev/null +++ b/lang/vendor/backup/pt/notifications.php @@ -0,0 +1,45 @@ + 'Mensagem de exceção: :message', + 'exception_trace' => 'Rasto da exceção: :trace', + 'exception_message_title' => 'Mensagem de exceção', + 'exception_trace_title' => 'Rasto da exceção', + + 'backup_failed_subject' => 'Falha no backup da aplicação :application_name', + 'backup_failed_body' => 'Importante: Ocorreu um erro ao executar o backup da aplicação :application_name', + + 'backup_successful_subject' => 'Backup realizado com sucesso: :application_name', + 'backup_successful_subject_title' => 'Backup Realizado com Sucesso!', + 'backup_successful_body' => 'Boas notícias, foi criado um novo backup no disco :disk_name referente à aplicação :application_name.', + + 'cleanup_failed_subject' => 'Falha na limpeza dos backups da aplicação :application_name.', + 'cleanup_failed_body' => 'Ocorreu um erro ao executar a limpeza dos backups da aplicação :application_name', + + 'cleanup_successful_subject' => 'Limpeza dos backups da aplicação :application_name concluída!', + 'cleanup_successful_subject_title' => 'Limpeza dos backups concluída!', + 'cleanup_successful_body' => 'Concluída a limpeza dos backups da aplicação :application_name no disco :disk_name.', + + 'healthy_backup_found_subject' => 'Os backups da aplicação :application_name no disco :disk_name estão em dia', + 'healthy_backup_found_subject_title' => 'Os backups da aplicação :application_name estão em dia', + 'healthy_backup_found_body' => 'Os backups da aplicação :application_name estão em dia. Bom trabalho!', + + 'unhealthy_backup_found_subject' => 'Importante: Os backups da aplicação :application_name não estão em dia', + 'unhealthy_backup_found_subject_title' => 'Importante: Os backups da aplicação :application_name não estão em dia. :problem', + 'unhealthy_backup_found_body' => 'Os backups da aplicação :application_name no disco :disk_name não estão em dia.', + 'unhealthy_backup_found_not_reachable' => 'O destino dos backups não pode ser alcançado. :error', + 'unhealthy_backup_found_empty' => 'Não existem backups para essa aplicação.', + 'unhealthy_backup_found_old' => 'O último backup realizado em :date é demasiado antigo.', + 'unhealthy_backup_found_unknown' => 'Desculpe, impossível determinar a razão exata.', + 'unhealthy_backup_found_full' => 'Os backups estão a utilizar demasiado espaço de armazenamento. A utilização atual é de :disk_usage, o que é maior que o limite permitido de :disk_limit.', + + 'no_backups_info' => 'Nenhum backup foi feito ainda', + 'application_name' => 'Nome da Aplicação', + 'backup_name' => 'Nome de backup', + 'disk' => 'Disco', + 'newest_backup_size' => 'Tamanho de backup mais recente', + 'number_of_backups' => 'Número de backups', + 'total_storage_used' => 'Armazenamento total usado', + 'newest_backup_date' => 'Data de backup mais recente', + 'oldest_backup_date' => 'Data de backup mais antiga', +]; diff --git a/lang/vendor/backup/pt_BR/notifications.php b/lang/vendor/backup/pt_BR/notifications.php new file mode 100644 index 0000000..406d4da --- /dev/null +++ b/lang/vendor/backup/pt_BR/notifications.php @@ -0,0 +1,45 @@ + 'Mensagem de exceção: :message', + 'exception_trace' => 'Rastreamento de exceção: :trace', + 'exception_message_title' => 'Mensagem de exceção', + 'exception_trace_title' => 'Rastreamento de exceção', + + 'backup_failed_subject' => 'Falha no backup da aplicação :application_name', + 'backup_failed_body' => 'Importante: Ocorreu um erro ao fazer o backup da aplicação :application_name', + + 'backup_successful_subject' => 'Backup realizado com sucesso: :application_name', + 'backup_successful_subject_title' => 'Backup Realizado com sucesso!', + 'backup_successful_body' => 'Boas notícias, um novo backup da aplicação :application_name foi criado no disco :disk_name.', + + 'cleanup_failed_subject' => 'Falha na limpeza dos backups da aplicação :application_name.', + 'cleanup_failed_body' => 'Um erro ocorreu ao fazer a limpeza dos backups da aplicação :application_name', + + 'cleanup_successful_subject' => 'Limpeza dos backups da aplicação :application_name concluída!', + 'cleanup_successful_subject_title' => 'Limpeza dos backups concluída!', + 'cleanup_successful_body' => 'A limpeza dos backups da aplicação :application_name no disco :disk_name foi concluída.', + + 'healthy_backup_found_subject' => 'Os backups da aplicação :application_name no disco :disk_name estão em dia', + 'healthy_backup_found_subject_title' => 'Os backups da aplicação :application_name estão em dia', + 'healthy_backup_found_body' => 'Os backups da aplicação :application_name estão em dia. Bom trabalho!', + + 'unhealthy_backup_found_subject' => 'Importante: Os backups da aplicação :application_name não estão em dia', + 'unhealthy_backup_found_subject_title' => 'Importante: Os backups da aplicação :application_name não estão em dia. :problem', + 'unhealthy_backup_found_body' => 'Os backups da aplicação :application_name no disco :disk_name não estão em dia.', + 'unhealthy_backup_found_not_reachable' => 'O destino dos backups não pode ser alcançado. :error', + 'unhealthy_backup_found_empty' => 'Não existem backups para essa aplicação.', + 'unhealthy_backup_found_old' => 'O último backup realizado em :date é considerado muito antigo.', + 'unhealthy_backup_found_unknown' => 'Desculpe, a exata razão não pode ser encontrada.', + 'unhealthy_backup_found_full' => 'Os backups estão usando muito espaço de armazenamento. A utilização atual é de :disk_usage, o que é maior que o limite permitido de :disk_limit.', + + 'no_backups_info' => 'Nenhum backup foi feito ainda', + 'application_name' => 'Nome da Aplicação', + 'backup_name' => 'Nome de backup', + 'disk' => 'Disco', + 'newest_backup_size' => 'Tamanho do backup mais recente', + 'number_of_backups' => 'Número de backups', + 'total_storage_used' => 'Armazenamento total usado', + 'newest_backup_date' => 'Data do backup mais recente', + 'oldest_backup_date' => 'Data do backup mais antigo', +]; diff --git a/lang/vendor/backup/ro/notifications.php b/lang/vendor/backup/ro/notifications.php new file mode 100644 index 0000000..0e8bc91 --- /dev/null +++ b/lang/vendor/backup/ro/notifications.php @@ -0,0 +1,45 @@ + 'Cu excepția mesajului: :message', + 'exception_trace' => 'Urmă excepţie: :trace', + 'exception_message_title' => 'Mesaj de excepție', + 'exception_trace_title' => 'Urmă excepţie', + + 'backup_failed_subject' => 'Nu s-a putut face copie de rezervă pentru :application_name', + 'backup_failed_body' => 'Important: A apărut o eroare în timpul generării copiei de rezervă pentru :application_name', + + 'backup_successful_subject' => 'Copie de rezervă efectuată cu succes pentru :application_name', + 'backup_successful_subject_title' => 'O nouă copie de rezervă a fost efectuată cu succes!', + 'backup_successful_body' => 'Vești bune, o nouă copie de rezervă pentru :application_name a fost creată cu succes pe discul cu numele :disk_name.', + + 'cleanup_failed_subject' => 'Curățarea copiilor de rezervă pentru :application_name nu a reușit.', + 'cleanup_failed_body' => 'A apărut o eroare în timpul curățirii copiilor de rezervă pentru :application_name', + + 'cleanup_successful_subject' => 'Curățarea copiilor de rezervă pentru :application_name a fost făcută cu succes', + 'cleanup_successful_subject_title' => 'Curățarea copiilor de rezervă a fost făcută cu succes!', + 'cleanup_successful_body' => 'Curățarea copiilor de rezervă pentru :application_name de pe discul cu numele :disk_name a fost făcută cu succes.', + + 'healthy_backup_found_subject' => 'Copiile de rezervă pentru :application_name de pe discul :disk_name sunt în regulă', + 'healthy_backup_found_subject_title' => 'Copiile de rezervă pentru :application_name sunt în regulă', + 'healthy_backup_found_body' => 'Copiile de rezervă pentru :application_name sunt considerate în regulă. Bună treabă!', + + 'unhealthy_backup_found_subject' => 'Important: Copiile de rezervă pentru :application_name nu sunt în regulă', + 'unhealthy_backup_found_subject_title' => 'Important: Copiile de rezervă pentru :application_name nu sunt în regulă. :problem', + 'unhealthy_backup_found_body' => 'Copiile de rezervă pentru :application_name de pe discul :disk_name nu sunt în regulă.', + 'unhealthy_backup_found_not_reachable' => 'Nu se poate ajunge la destinația copiilor de rezervă. :error', + 'unhealthy_backup_found_empty' => 'Nu există copii de rezervă ale acestei aplicații.', + 'unhealthy_backup_found_old' => 'Cea mai recentă copie de rezervă făcută la :date este considerată prea veche.', + 'unhealthy_backup_found_unknown' => 'Ne pare rău, un motiv exact nu poate fi determinat.', + 'unhealthy_backup_found_full' => 'Copiile de rezervă folosesc prea mult spațiu de stocare. Utilizarea curentă este de :disk_usage care este mai mare decât limita permisă de :disk_limit.', + + 'no_backups_info' => 'Nu s-au făcut încă copii de rezervă', + 'application_name' => 'Numele aplicatiei', + 'backup_name' => 'Numele de rezervă', + 'disk' => 'Disc', + 'newest_backup_size' => 'Cea mai nouă dimensiune de rezervă', + 'number_of_backups' => 'Număr de copii de rezervă', + 'total_storage_used' => 'Spațiu total de stocare utilizat', + 'newest_backup_date' => 'Cea mai nouă dimensiune de rezervă', + 'oldest_backup_date' => 'Cea mai veche dimensiune de rezervă', +]; diff --git a/lang/vendor/backup/ru/notifications.php b/lang/vendor/backup/ru/notifications.php new file mode 100644 index 0000000..d58beb7 --- /dev/null +++ b/lang/vendor/backup/ru/notifications.php @@ -0,0 +1,45 @@ + 'Сообщение об ошибке: :message', + 'exception_trace' => 'Сведения об ошибке: :trace', + 'exception_message_title' => 'Сообщение об ошибке', + 'exception_trace_title' => 'Сведения об ошибке', + + 'backup_failed_subject' => 'Не удалось сделать резервную копию :application_name', + 'backup_failed_body' => 'Внимание: Произошла ошибка во время резервного копирования :application_name', + + 'backup_successful_subject' => 'Успешно создана новая резервная копия :application_name', + 'backup_successful_subject_title' => 'Успешно создана новая резервная копия!', + 'backup_successful_body' => 'Отличная новость, новая резервная копия :application_name успешно создана и сохранена на диск :disk_name.', + + 'cleanup_failed_subject' => 'Не удалось очистить резервные копии :application_name', + 'cleanup_failed_body' => 'Произошла ошибка при очистке резервных копий :application_name', + + 'cleanup_successful_subject' => 'Очистка от резервных копий :application_name прошла успешно', + 'cleanup_successful_subject_title' => 'Очистка резервных копий прошла успешно!', + 'cleanup_successful_body' => 'Очистка от старых резервных копий :application_name на диске :disk_name прошла успешно.', + + 'healthy_backup_found_subject' => 'Резервные копии :application_name с диска :disk_name исправны', + 'healthy_backup_found_subject_title' => 'Резервные копии :application_name исправны', + 'healthy_backup_found_body' => 'Резервные копии :application_name считаются исправными. Хорошая работа!', + + 'unhealthy_backup_found_subject' => 'Внимание: резервные копии :application_name неисправны', + 'unhealthy_backup_found_subject_title' => 'Внимание: резервные копии для :application_name неисправны. :problem', + 'unhealthy_backup_found_body' => 'Резервные копии для :application_name на диске :disk_name неисправны.', + 'unhealthy_backup_found_not_reachable' => 'Не удается достичь места назначения резервной копии. :error', + 'unhealthy_backup_found_empty' => 'Резервные копии для этого приложения отсутствуют.', + 'unhealthy_backup_found_old' => 'Последнее резервное копирование созданное :date является устаревшим.', + 'unhealthy_backup_found_unknown' => 'Извините, точная причина не может быть определена.', + 'unhealthy_backup_found_full' => 'Резервные копии используют слишком много памяти. Используется :disk_usage что выше допустимого предела: :disk_limit.', + + 'no_backups_info' => 'Резервных копий еще не было', + 'application_name' => 'Имя приложения', + 'backup_name' => 'Имя резервной копии', + 'disk' => 'Диск', + 'newest_backup_size' => 'Размер последней резервной копии', + 'number_of_backups' => 'Количество резервных копий', + 'total_storage_used' => 'Общий объем используемого хранилища', + 'newest_backup_date' => 'Дата последней резервной копии', + 'oldest_backup_date' => 'Дата самой старой резервной копии', +]; diff --git a/lang/vendor/backup/sk/notifications.php b/lang/vendor/backup/sk/notifications.php new file mode 100644 index 0000000..1d95e44 --- /dev/null +++ b/lang/vendor/backup/sk/notifications.php @@ -0,0 +1,45 @@ + 'Správa výnimky: :message', + 'exception_trace' => 'Stopa výnimky: :trace', + 'exception_message_title' => 'Správa výnimky', + 'exception_trace_title' => 'Stopa výnimky', + + 'backup_failed_subject' => 'Záloha :application_name zlyhala', + 'backup_failed_body' => 'Dôležité: Pri zálohovaní :application_name sa vyskytla chyba', + + 'backup_successful_subject' => 'Úspešná nová záloha :application_name', + 'backup_successful_subject_title' => 'Úspešná nová záloha!', + 'backup_successful_body' => 'Dobrá správa, na disku s názvom :disk_name bola úspešne vytvorená nová záloha :application_name.', + + 'cleanup_failed_subject' => 'Vyčistenie záloh :application_name zlyhalo.', + 'cleanup_failed_body' => 'Pri čistení záloh :application_name sa vyskytla chyba', + + 'cleanup_successful_subject' => 'Vyčistenie záloh :application_name bolo úspešné', + 'cleanup_successful_subject_title' => 'Vyčistenie záloh bolo úspešné!', + 'cleanup_successful_body' => 'Vyčistenie záloh :application_name na disku s názvom :disk_name bolo úspešné.', + + 'healthy_backup_found_subject' => 'Zálohy pre :application_name na disku :disk_name sú zdravé', + 'healthy_backup_found_subject_title' => 'Zálohy pre :application_name sú zdravé', + 'healthy_backup_found_body' => 'Zálohy pre :application_name sa považujú za zdravé. Dobrá práca!', + + 'unhealthy_backup_found_subject' => 'Dôležité: Zálohy pre :application_name sú nezdravé', + 'unhealthy_backup_found_subject_title' => 'Dôležité: Zálohy pre :application_name sú nezdravé. :problem', + 'unhealthy_backup_found_body' => 'Zálohy pre :application_name na disku :disk_name sú nezdravé.', + 'unhealthy_backup_found_not_reachable' => 'Nemožno sa dostať k cieľu zálohy. :error', + 'unhealthy_backup_found_empty' => 'Táto aplikácia nemá žiadne zálohy.', + 'unhealthy_backup_found_old' => 'Posledná záloha vytvorená dňa :date sa považuje za príliš starú.', + 'unhealthy_backup_found_unknown' => 'Ospravedlňujeme sa, nemôžeme určiť presný dôvod.', + 'unhealthy_backup_found_full' => 'Zálohy zaberajú príliš veľa miesta na disku. Aktuálne využitie disku je :disk_usage, čo je viac ako povolený limit :disk_limit.', + + 'no_backups_info' => 'Zatiaľ neboli vytvorené žiadne zálohy', + 'application_name' => 'Názov aplikácie', + 'backup_name' => 'Názov zálohy', + 'disk' => 'Disk', + 'newest_backup_size' => 'Veľkosť najnovšej zálohy', + 'number_of_backups' => 'Počet záloh', + 'total_storage_used' => 'Celková využitá kapacita úložiska', + 'newest_backup_date' => 'Dátum najnovšej zálohy', + 'oldest_backup_date' => 'Dátum najstaršej zálohy', +]; diff --git a/lang/vendor/backup/tr/notifications.php b/lang/vendor/backup/tr/notifications.php new file mode 100644 index 0000000..64cfa5a --- /dev/null +++ b/lang/vendor/backup/tr/notifications.php @@ -0,0 +1,45 @@ + 'Hata mesajı: :message', + 'exception_trace' => 'Hata izleri: :trace', + 'exception_message_title' => 'Hata mesajı', + 'exception_trace_title' => 'Hata izleri', + + 'backup_failed_subject' => 'Yedeklenemedi :application_name', + 'backup_failed_body' => 'Önemli: Yedeklenirken bir hata oluştu :application_name', + + 'backup_successful_subject' => 'Başarılı :application_name yeni yedeklemesi', + 'backup_successful_subject_title' => 'Başarılı bir yeni yedekleme!', + 'backup_successful_body' => 'Harika bir haber, :application_name ait yeni bir yedekleme :disk_name adlı diskte başarıyla oluşturuldu.', + + 'cleanup_failed_subject' => ':application_name yedeklemeleri temizlenmesi başarısız.', + 'cleanup_failed_body' => ':application_name yedeklerini temizlerken bir hata oluştu ', + + 'cleanup_successful_subject' => ':application_name yedeklemeleri temizlenmesi başarılı.', + 'cleanup_successful_subject_title' => 'Yedeklerin temizlenmesi başarılı!', + 'cleanup_successful_body' => ':application_name yedeklemeleri temizlenmesi, :disk_name diskinden silindi', + + 'healthy_backup_found_subject' => ':application_name yedeklenmesi, :disk_name adlı diskte sağlıklı', + 'healthy_backup_found_subject_title' => ':application_name yedeklenmesi sağlıklı', + 'healthy_backup_found_body' => ':application_name için yapılan yedeklemeler sağlıklı sayılır. Aferin!', + + 'unhealthy_backup_found_subject' => 'Önemli: :application_name için yedeklemeler sağlıksız', + 'unhealthy_backup_found_subject_title' => 'Önemli: :application_name için yedeklemeler sağlıksız. :problem', + 'unhealthy_backup_found_body' => 'Yedeklemeler: :application_name disk: :disk_name sağlıksız.', + 'unhealthy_backup_found_not_reachable' => 'Yedekleme hedefine ulaşılamıyor. :error', + 'unhealthy_backup_found_empty' => 'Bu uygulamanın yedekleri yok.', + 'unhealthy_backup_found_old' => ':date tarihinde yapılan en son yedekleme çok eski kabul ediliyor.', + 'unhealthy_backup_found_unknown' => 'Üzgünüm, kesin bir sebep belirlenemiyor.', + 'unhealthy_backup_found_full' => 'Yedeklemeler çok fazla depolama alanı kullanıyor. Şu anki kullanım: :disk_usage, izin verilen sınırdan yüksek: :disk_limit.', + + 'no_backups_info' => 'Henüz yedekleme yapılmadı', + 'application_name' => 'Uygulama Adı', + 'backup_name' => 'Yedek adı', + 'disk' => 'Disk', + 'newest_backup_size' => 'En yeni yedekleme boyutu', + 'number_of_backups' => 'Yedekleme sayısı', + 'total_storage_used' => 'Kullanılan toplam depolama alanı', + 'newest_backup_date' => 'En yeni yedekleme tarihi', + 'oldest_backup_date' => 'En eski yedekleme tarihi', +]; diff --git a/lang/vendor/backup/uk/notifications.php b/lang/vendor/backup/uk/notifications.php new file mode 100644 index 0000000..6f6f83b --- /dev/null +++ b/lang/vendor/backup/uk/notifications.php @@ -0,0 +1,45 @@ + 'Повідомлення про помилку: :message', + 'exception_trace' => 'Деталі помилки: :trace', + 'exception_message_title' => 'Повідомлення помилки', + 'exception_trace_title' => 'Деталі помилки', + + 'backup_failed_subject' => 'Не вдалось зробити резервну копію :application_name', + 'backup_failed_body' => 'Увага: Трапилась помилка під час резервного копіювання :application_name', + + 'backup_successful_subject' => 'Успішне резервне копіювання :application_name', + 'backup_successful_subject_title' => 'Успішно створена резервна копія!', + 'backup_successful_body' => 'Чудова новина, нова резервна копія :application_name успішно створена і збережена на диск :disk_name.', + + 'cleanup_failed_subject' => 'Не вдалось очистити резервні копії :application_name', + 'cleanup_failed_body' => 'Сталася помилка під час очищення резервних копій :application_name', + + 'cleanup_successful_subject' => 'Успішне очищення від резервних копій :application_name', + 'cleanup_successful_subject_title' => 'Очищення резервних копій пройшло вдало!', + 'cleanup_successful_body' => 'Очищенно від старих резервних копій :application_name на диску :disk_name пойшло успішно.', + + 'healthy_backup_found_subject' => 'Резервна копія :application_name з диску :disk_name установлена', + 'healthy_backup_found_subject_title' => 'Резервна копія :application_name установлена', + 'healthy_backup_found_body' => 'Резервна копія :application_name успішно установлена. Хороша робота!', + + 'unhealthy_backup_found_subject' => 'Увага: резервна копія :application_name не установилась', + 'unhealthy_backup_found_subject_title' => 'Увага: резервна копія для :application_name не установилась. :problem', + 'unhealthy_backup_found_body' => 'Резервна копія для :application_name на диску :disk_name не установилась.', + 'unhealthy_backup_found_not_reachable' => 'Резервна копія не змогла установитись. :error', + 'unhealthy_backup_found_empty' => 'Резервні копії для цього додатку відсутні.', + 'unhealthy_backup_found_old' => 'Останнє резервне копіювання створено :date є застарілим.', + 'unhealthy_backup_found_unknown' => 'Вибачте, але ми не змогли визначити точну причину.', + 'unhealthy_backup_found_full' => 'Резервні копії використовують занадто багато пам`яті. Використовується :disk_usage що вище за допустиму межу :disk_limit.', + + 'no_backups_info' => 'Резервних копій ще не було зроблено', + 'application_name' => 'Назва програми', + 'backup_name' => 'Резервне ім’я', + 'disk' => 'Диск', + 'newest_backup_size' => 'Найновіший розмір резервної копії', + 'number_of_backups' => 'Кількість резервних копій', + 'total_storage_used' => 'Загальний обсяг використаного сховища', + 'newest_backup_date' => 'Найновіший розмір резервної копії', + 'oldest_backup_date' => 'Найстаріший розмір резервної копії', +]; diff --git a/lang/vendor/backup/zh_CN/notifications.php b/lang/vendor/backup/zh_CN/notifications.php new file mode 100644 index 0000000..7927084 --- /dev/null +++ b/lang/vendor/backup/zh_CN/notifications.php @@ -0,0 +1,45 @@ + '异常信息: :message', + 'exception_trace' => '异常跟踪: :trace', + 'exception_message_title' => '异常信息', + 'exception_trace_title' => '异常跟踪', + + 'backup_failed_subject' => ':application_name 备份失败', + 'backup_failed_body' => '重要说明:备份 :application_name 时发生错误', + + 'backup_successful_subject' => ':application_name 备份成功', + 'backup_successful_subject_title' => '备份成功!', + 'backup_successful_body' => '好消息, :application_name 备份成功,位于磁盘 :disk_name 中。', + + 'cleanup_failed_subject' => '清除 :application_name 的备份失败。', + 'cleanup_failed_body' => '清除备份 :application_name 时发生错误', + + 'cleanup_successful_subject' => '成功清除 :application_name 的备份', + 'cleanup_successful_subject_title' => '成功清除备份!', + 'cleanup_successful_body' => '成功清除 :disk_name 磁盘上 :application_name 的备份。', + + 'healthy_backup_found_subject' => ':disk_name 磁盘上 :application_name 的备份是健康的', + 'healthy_backup_found_subject_title' => ':application_name 的备份是健康的', + 'healthy_backup_found_body' => ':application_name 的备份是健康的。干的好!', + + 'unhealthy_backup_found_subject' => '重要说明::application_name 的备份不健康', + 'unhealthy_backup_found_subject_title' => '重要说明::application_name 备份不健康。 :problem', + 'unhealthy_backup_found_body' => ':disk_name 磁盘上 :application_name 的备份不健康。', + 'unhealthy_backup_found_not_reachable' => '无法访问备份目标。 :error', + 'unhealthy_backup_found_empty' => '根本没有此应用程序的备份。', + 'unhealthy_backup_found_old' => '最近的备份创建于 :date ,太旧了。', + 'unhealthy_backup_found_unknown' => '对不起,确切原因无法确定。', + 'unhealthy_backup_found_full' => '备份占用了太多存储空间。当前占用了 :disk_usage ,高于允许的限制 :disk_limit。', + + 'no_backups_info' => '尚未进行任何备份', + 'application_name' => '应用名称', + 'backup_name' => '备份名称', + 'disk' => '磁盘', + 'newest_backup_size' => '最新备份大小', + 'number_of_backups' => '备份数量', + 'total_storage_used' => '使用的总存储量', + 'newest_backup_date' => '最新备份大小', + 'oldest_backup_date' => '最旧的备份大小', +]; diff --git a/lang/vendor/backup/zh_TW/notifications.php b/lang/vendor/backup/zh_TW/notifications.php new file mode 100644 index 0000000..7bc7dcb --- /dev/null +++ b/lang/vendor/backup/zh_TW/notifications.php @@ -0,0 +1,45 @@ + '異常訊息: :message', + 'exception_trace' => '異常追蹤: :trace', + 'exception_message_title' => '異常訊息', + 'exception_trace_title' => '異常追蹤', + + 'backup_failed_subject' => ':application_name 備份失敗', + 'backup_failed_body' => '重要說明:備份 :application_name 時發生錯誤', + + 'backup_successful_subject' => ':application_name 備份成功', + 'backup_successful_subject_title' => '備份成功!', + 'backup_successful_body' => '好消息, :application_name 備份成功,位於磁碟 :disk_name 中。', + + 'cleanup_failed_subject' => '清除 :application_name 的備份失敗。', + 'cleanup_failed_body' => '清除備份 :application_name 時發生錯誤', + + 'cleanup_successful_subject' => '成功清除 :application_name 的備份', + 'cleanup_successful_subject_title' => '成功清除備份!', + 'cleanup_successful_body' => '成功清除 :disk_name 磁碟上 :application_name 的備份。', + + 'healthy_backup_found_subject' => ':disk_name 磁碟上 :application_name 的備份是健康的', + 'healthy_backup_found_subject_title' => ':application_name 的備份是健康的', + 'healthy_backup_found_body' => ':application_name 的備份是健康的。幹的好!', + + 'unhealthy_backup_found_subject' => '重要說明::application_name 的備份不健康', + 'unhealthy_backup_found_subject_title' => '重要說明::application_name 備份不健康。 :problem', + 'unhealthy_backup_found_body' => ':disk_name 磁碟上 :application_name 的備份不健康。', + 'unhealthy_backup_found_not_reachable' => '無法訪問備份目標。 :error', + 'unhealthy_backup_found_empty' => '根本沒有此應用程序的備份。', + 'unhealthy_backup_found_old' => '最近的備份創建於 :date ,太舊了。', + 'unhealthy_backup_found_unknown' => '對不起,確切原因無法確定。', + 'unhealthy_backup_found_full' => '備份佔用了太多存儲空間。當前佔用了 :disk_usage ,高於允許的限制 :disk_limit。', + + 'no_backups_info' => '尚未進行任何備份', + 'application_name' => '應用名稱', + 'backup_name' => '備份名稱', + 'disk' => '磁碟', + 'newest_backup_size' => '最新備份大小', + 'number_of_backups' => '備份數量', + 'total_storage_used' => '使用的總存儲量', + 'newest_backup_date' => '最新備份大小', + 'oldest_backup_date' => '最早的備份大小', +];