Rutas del sistema y archivos de configuración

This commit is contained in:
Lucho
2026-06-24 16:29:04 -03:00
parent c81120f2e3
commit 9474236226
6 changed files with 6856 additions and 13 deletions
+3 -3
View File
@@ -39,9 +39,9 @@ use App\Http\Controllers\UbicacionController;
use App\Http\Controllers\UserController;
Route::middleware('api')->group(function () {
Route::post('auth/login/cliente', [AuthController::class, 'loginCliente']);
Route::post('auth/login/personal', [AuthController::class, 'loginPersonal']);
Route::post('auth/login', [AuthController::class, 'login']);
Route::post('auth/login/cliente', [AuthController::class, 'loginCliente'])->middleware('throttle:login-cliente-api');
Route::post('auth/login/personal', [AuthController::class, 'loginPersonal'])->middleware('throttle:login-personal-api');
Route::post('auth/login', [AuthController::class, 'login'])->middleware('throttle:login-api-general');
Route::post('auth/logout', [AuthController::class, 'logout']);
// Rutas API Resource estándar
+225
View File
@@ -1,8 +1,233 @@
<?php
use App\Models\Administrador;
use App\Models\Formulario;
use App\Models\LogSeguridad;
use Database\Seeders\BulkDataSeeder;
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Schedule;
use Illuminate\Support\Facades\Storage;
Artisan::command('inspire', function () {
$this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');
Artisan::command('logs:purge-old-security', function () {
$fechaLimite = now()->subMonths(2);
$eliminados = LogSeguridad::query()
->where('fechahora', '<', $fechaLimite)
->delete();
$this->info('Logs eliminados: ' . $eliminados);
})->purpose('Elimina logs de seguridad con más de 2 meses de antigüedad');
Artisan::command('seed:bulk {--modo=masivo} {--clientes=} {--profesionales=} {--servicios=} {--profesiones=} {--fresh}', function () {
$presets = [
'rapido' => ['clientes' => 100, 'profesionales' => 20, 'servicios' => 10, 'profesiones' => 2],
'medio' => ['clientes' => 500, 'profesionales' => 50, 'servicios' => 20, 'profesiones' => 3],
'masivo' => ['clientes' => 1000, 'profesionales' => 100, 'servicios' => 30, 'profesiones' => 4],
];
$modo = strtolower((string) $this->option('modo'));
if (!array_key_exists($modo, $presets)) {
$this->error('Modo invalido. Usa: rapido, medio o masivo.');
return 1;
}
$clientes = max(0, (int) ($this->option('clientes') ?? $presets[$modo]['clientes']));
$profesionales = max(0, (int) ($this->option('profesionales') ?? $presets[$modo]['profesionales']));
$servicios = max(0, (int) ($this->option('servicios') ?? $presets[$modo]['servicios']));
$profesiones = max(0, (int) ($this->option('profesiones') ?? $presets[$modo]['profesiones']));
config([
'bulk_seed.enabled' => false,
'bulk_seed.clientes' => $clientes,
'bulk_seed.profesionales' => $profesionales,
'bulk_seed.servicios' => $servicios,
'bulk_seed.profesiones' => $profesiones,
]);
if ((bool) $this->option('fresh')) {
$this->info('Ejecutando migrate:fresh --seed...');
$exitMigrate = Artisan::call('migrate:fresh', ['--seed' => true]);
$this->line(Artisan::output());
if ($exitMigrate !== 0) {
$this->error('Fallo migrate:fresh --seed');
return $exitMigrate;
}
}
$this->info("Cargando datos masivos (modo={$modo})...");
$exitBulk = Artisan::call('db:seed', ['--class' => BulkDataSeeder::class]);
$this->line(Artisan::output());
if ($exitBulk !== 0) {
$this->error('Fallo la ejecucion de BulkDataSeeder.');
return $exitBulk;
}
$this->info("Datos creados: {$profesiones} profesiones, {$servicios} servicios, {$clientes} clientes y {$profesionales} profesionales.");
return 0;
})->purpose('Carga datos de prueba masivos por modo o por cantidades personalizadas');
Artisan::command('formularios:sync-rejected-status', function () {
$formulariosPendientes = Formulario::query()
->where('estado', 'Pendiente')
->get(['id', 'profesion_id']);
$actualizados = 0;
foreach ($formulariosPendientes as $formulario) {
$formularioId = (int) $formulario->id;
$profesionFormularioId = (int) $formulario->profesion_id;
$asignadosActivos = DB::table('profesionales_formularios as pf')
->join('profesionales as p', 'p.id', '=', 'pf.profesional_id')
->where('pf.formulario_id', $formularioId)
->where('p.baja_id', 1)
->where(function ($query) use ($profesionFormularioId): void {
$query->where('p.profesion_id', $profesionFormularioId)
->orWhereExists(function ($subquery) use ($profesionFormularioId): void {
$subquery->select(DB::raw(1))
->from('profesionales_profesiones as pp')
->whereColumn('pp.profesional_id', 'p.id')
->where('pp.profesion_id', $profesionFormularioId);
});
})
->count();
if ($asignadosActivos === 0) {
continue;
}
$hayActivosSinRechazar = DB::table('profesionales_formularios as pf')
->join('profesionales as p', 'p.id', '=', 'pf.profesional_id')
->where('pf.formulario_id', $formularioId)
->where('p.baja_id', 1)
->where(function ($query) use ($profesionFormularioId): void {
$query->where('p.profesion_id', $profesionFormularioId)
->orWhereExists(function ($subquery) use ($profesionFormularioId): void {
$subquery->select(DB::raw(1))
->from('profesionales_profesiones as pp')
->whereColumn('pp.profesional_id', 'p.id')
->where('pp.profesion_id', $profesionFormularioId);
});
})
->whereRaw("LOWER(TRIM(pf.estado)) <> 'rechazado'")
->exists();
if (!$hayActivosSinRechazar) {
Formulario::query()
->where('id', $formularioId)
->update(['estado' => 'Rechazado por todos']);
$actualizados++;
}
}
$this->info('Formularios actualizados: ' . $actualizados);
})->purpose('Sincroniza formularios pendientes a Rechazado por todos cuando todos los profesionales activos rechazaron');
Artisan::command('backup:daily-email {--keep=4}', function () {
$includeEnv = (bool) filter_var((string) env('BACKUP_INCLUDE_ENV', false), FILTER_VALIDATE_BOOLEAN);
$archivePassword = trim((string) env('BACKUP_ARCHIVE_PASSWORD', ''));
if ($includeEnv && $archivePassword === '') {
$this->error('BACKUP_INCLUDE_ENV=true requiere BACKUP_ARCHIVE_PASSWORD configurado para cifrar el ZIP.');
return 1;
}
$this->info('Iniciando backup semanal de base de datos y archivos...');
$backupExitCode = Artisan::call('backup:run');
$this->line(Artisan::output());
if ($backupExitCode !== 0) {
$this->error('No se pudo generar el backup.');
return 1;
}
$disk = Storage::disk('local');
$backupFolder = trim((string) config('backup.backup.name', 'Laravel'), '/');
$files = collect($disk->files($backupFolder))
->filter(fn (string $file): bool => str_ends_with(strtolower($file), '.zip'))
->sortByDesc(fn (string $file): int => $disk->lastModified($file))
->values();
if ($files->isEmpty()) {
$this->error('No se encontró el archivo ZIP del backup para enviar por email.');
return 1;
}
$latestBackup = (string) $files->first();
$adminRecipients = Administrador::query()
->select('correo')
->whereNotNull('correo')
->pluck('correo')
->map(fn ($correo): string => trim((string) $correo))
->filter(fn (string $correo): bool => $correo !== '' && filter_var($correo, FILTER_VALIDATE_EMAIL) !== false)
->unique()
->values()
->all();
$fallbackRecipient = (string) env('BACKUP_MAIL_TO', (string) config('backup.notifications.mail.to'));
$recipients = !empty($adminRecipients)
? $adminRecipients
: array_values(array_filter([(string) $fallbackRecipient]));
if (empty($recipients)) {
$this->error('No hay correos de administradores ni BACKUP_MAIL_TO configurado para enviar el backup.');
return 1;
}
try {
Mail::raw('Se adjunta el backup semanal de base de datos y archivos.', function ($message) use ($disk, $latestBackup, $recipients): void {
$message->to($recipients)
->subject('Backup semanal - ' . now()->format('d/m/Y'))
->attachData(
$disk->get($latestBackup),
basename($latestBackup),
['mime' => 'application/zip']
);
});
} catch (\Throwable $e) {
$this->error('El backup se generó, pero falló el envío de email: ' . $e->getMessage());
return 1;
}
$keep = max(1, (int) $this->option('keep'));
$files->slice($keep)->each(function (string $file) use ($disk): void {
$disk->delete($file);
});
$deletedCount = max(0, $files->count() - $keep);
$this->info('Backup enviado a: ' . implode(', ', $recipients));
$this->info('Backups eliminados por retención: ' . $deletedCount);
$this->info('Backups conservados: ' . $keep);
return 0;
})->purpose('Genera backup semanal de BD y archivos, lo envía por mail y conserva solo los últimos N backups');
Schedule::command('logs:purge-old-security')->daily();
Schedule::command('backup:daily-email --keep=4')->dailyAt('16:00'); //ejecuta backup:run
Schedule::command('backup:clean')->dailyAt('16:20');
Schedule::command('backup:monitor')->dailyAt('16:30');
+6272 -5
View File
File diff suppressed because it is too large Load Diff