Actualización de lector con código qr

This commit is contained in:
2026-02-13 17:12:28 -06:00
parent 5fe41a4557
commit d88f33ccf4
14 changed files with 358 additions and 61 deletions
+7 -7
View File
@@ -2,10 +2,11 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Routing\Controller;
use App\Models\Plantel;
use App\Models\User; use App\Models\User;
use App\Models\Plantel;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\DB;
use Spatie\Permission\Models\Role; use Spatie\Permission\Models\Role;
class UserController extends Controller class UserController extends Controller
@@ -29,11 +30,11 @@ class UserController extends Controller
public function create() public function create()
{ {
$plantels = Plantel::where('status','=','1')->get(); $plantels = Plantel::where('status','=','1')->get();
$codigos = DB::table('area_code')->get();
$roles= Role::all(); $roles= Role::all();
return view('user.create',compact('roles','plantels')); return view('user.create',compact('roles','plantels','codigos'));
} }
public function store(Request $request,User $user) public function store(Request $request,User $user)
{ {
$user= User::create($request->except(['plantels'])); $user= User::create($request->except(['plantels']));
@@ -44,18 +45,17 @@ class UserController extends Controller
return redirect()->route('user.edit',compact('user'))->with('info','Se creó el usuario correctamente'); return redirect()->route('user.edit',compact('user'))->with('info','Se creó el usuario correctamente');
} }
public function show(User $user) public function show(User $user)
{ {
return view('user.show',compact('user')); return view('user.show',compact('user'));
} }
public function edit(User $user) public function edit(User $user)
{ {
$plantels=Plantel::all(); $plantels=Plantel::all();
$codigos = DB::table('area_code')->get();
$roles= Role::all(); $roles= Role::all();
return view('user.edit',compact('user','roles','plantels')); return view('user.edit',compact('user','roles','plantels','codigos'));
} }
public function update(Request $request, User $user) public function update(Request $request, User $user)
+231 -8
View File
@@ -2,8 +2,13 @@
namespace App\Livewire; namespace App\Livewire;
use Carbon\Carbon;
use App\Models\Code;
use App\Models\User;
use Livewire\Component; use Livewire\Component;
use Livewire\Attributes\On; use Livewire\Attributes\On;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth;
class CodeScanner extends Component class CodeScanner extends Component
{ {
@@ -12,15 +17,233 @@ class CodeScanner extends Component
public function qrLeido($texto, $lat, $lng) public function qrLeido($texto, $lat, $lng)
{ {
$dia = now()->translatedFormat('l');
dd($dia);
$this->dispatch('swal', $codigo = DB::table('area_code')
icon: 'success', ->where('name', $texto)
title: 'Asistencia registrada!', ->first();
text: $lat,
timer: 2000, $corporativo = DB::table('area_code')
confirm: false, ->where('name','like', '%COR%')
closeModal: true ->first();
);
$user = Auth::user();
if($user->code == $corporativo->id){
if($codigo){
$planteles = $user->plantelUsuarios;
$match=0;
foreach($planteles as $plantel){
$latCentro = $plantel->latitud;
$lngCentro = $plantel->longitud;
$radioPermitido = 100; // metros
$distancia = $this->calcularDistancia($lat,$lng,$latCentro,$lngCentro);
if($distancia > $radioPermitido){
$match=1;
}else{
$match = 0;
break;
}
}
if($match == 1){
$this->dispatch('swal',
icon: 'error',
title: 'Error!',
text: 'Fuera del area permitida',
confirm: false,
closeModal: true
);
return;
}
$hoy = Carbon::today();
$asistenciasHoy = Code::where('user_id',$user->id)
->whereDate('registro',$hoy)
->orderBy('registro','desc')
->get();
if($asistenciasHoy->count() >= 4){
$this->dispatch('swal',
icon: 'warning',
title: 'Chequeos completos!',
text: 'Límite de registros alcanzados',
confirm: false,
closeModal: true,
);
return;
}
if($asistenciasHoy->count() > 0){
$ultima = Code::where('user_id',$user->id)
->whereDate('registro', today())
->latest('registro')
->first();
if($ultima->registro->diffInMinutes(now()) < 40){
$this->dispatch('swal',
icon: 'warning',
title: 'Espera!',
text: 'podrás checar 40 minutos despues de tu ultimo registro',
confirm: false,
closeModal: true,
);
return;
}
}
Code::create([
'user_id' => $user->id,
'codigo' => $texto,
'registro' => Carbon::now('America/Mexico_City')->format('d-m-Y H:i:s'),
'latitud' => $lat,
'longitud' => $lng,
]);
$this->dispatch('swal',
icon: 'success',
title: 'Excelente!',
text: 'asistencia registrada',
confirm: false,
closeModal: true,
reload:true
);
}else{
$this->dispatch('swal',
icon: 'error',
title: 'Código inexistente',
text: 'Este codigo no es institucional',
confirm: false,
closeModal: true
);
return;
}
}else{
if($codigo->id != $user->code){
$this->dispatch('swal',
icon: 'error',
title: 'Error!',
text: 'Este codigo no corresponde a tu area',
confirm: false,
closeModal: true
);
return;
}
$planteles = $user->plantelUsuarios;
$match=0;
foreach($planteles as $plantel){
$latCentro = $plantel->latitud;
$lngCentro = $plantel->longitud;
$radioPermitido = 100; // metros
$distancia = $this->calcularDistancia($lat,$lng,$latCentro,$lngCentro);
if($distancia > $radioPermitido){
$match=1;
}else{
$match = 0;
break;
}
}
if($match == 1){
$this->dispatch('swal',
icon: 'error',
title: 'Error!',
text: 'Fuera del area permitida',
confirm: false,
closeModal: true
);
return;
}
$hoy = Carbon::today();
$asistenciasHoy = Code::where('user_id',$user->id)
->whereDate('registro',$hoy)
->orderBy('registro','desc')
->get();
if($asistenciasHoy->count() >= 4){
$this->dispatch('swal',
icon: 'warning',
title: 'Chequeos completos!',
text: 'Límite de registros alcanzados',
confirm: false,
closeModal: true,
);
return;
}
if($asistenciasHoy->count() > 0){
$ultima = $asistenciasHoy->first();
if($ultima->registro->diffInMinutes(now()) < 40){
$this->dispatch('swal',
icon:'info',
title:'Aún no pasan 40 minutos'
);
return;
}
}
Code::create([
'user_id' => $user->id,
'codigo' => $texto,
'registro' => Carbon::now('America/Mexico_City')->format('d-m-Y H:i:s'),
'latitud' => $lat,
'longitud' => $lng,
]);
$this->dispatch('swal',
icon: 'success',
title: 'Excelente!',
text: 'asistencia registrada',
confirm: false,
closeModal: true,
reload:true
);
}
}
private function calcularDistancia($lat1,$lon1,$lat2,$lon2)
{
$radioTierra = 6371000;
$dLat = deg2rad($lat2 - $lat1);
$dLon = deg2rad($lon2 - $lon1);
$a = sin($dLat/2) * sin($dLat/2) +
cos(deg2rad($lat1)) *
cos(deg2rad($lat2)) *
sin($dLon/2) * sin($dLon/2);
$c = 2 * atan2(sqrt($a), sqrt(1-$a));
return $radioTierra * $c;
} }
public function render() public function render()
-12
View File
@@ -11,7 +11,6 @@ use Laravel\Fortify\TwoFactorAuthenticatable;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable class User extends Authenticatable
@@ -25,11 +24,6 @@ class User extends Authenticatable
use TwoFactorAuthenticatable; use TwoFactorAuthenticatable;
use HasRoles; use HasRoles;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [ protected $fillable = [
'name', 'name',
'email', 'email',
@@ -50,16 +44,10 @@ class User extends Authenticatable
'status', 'status',
]; ];
protected $appends = [ protected $appends = [
'profile_photo_url', 'profile_photo_url',
]; ];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array protected function casts(): array
{ {
return [ return [
+1 -1
View File
@@ -65,7 +65,7 @@ return [
| |
*/ */
'timezone' => 'UTC', 'timezone' => 'America/Mexico_City',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@@ -15,7 +15,10 @@ return new class extends Migration
$table->id(); $table->id();
$table->string('name'); $table->string('name');
$table->string('razon'); $table->string('razon');
$table->string('ubicacion'); $table->string('ubicacion')->nullable();;
$table->string('nomenclatura')->nullable();
$table->string('latitud')->nullable();
$table->string('longitud')->nullable();
$table->bigInteger('telefono'); $table->bigInteger('telefono');
$table->boolean('status'); $table->boolean('status');
$table->timestamps(); $table->timestamps();
@@ -6,14 +6,13 @@ use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class extends Migration
{ {
/**
* Run the migrations.
*/
public function up(): void public function up(): void
{ {
Schema::create('codes', function (Blueprint $table) { Schema::create('codes', function (Blueprint $table) {
$table->id(); $table->id();
$table->unsignedBigInteger('user_id'); $table->unsignedBigInteger('user_id');
$table->string('codigo');
$table->dateTime('registro')->nullable(); $table->dateTime('registro')->nullable();
$table->string('latitud'); $table->string('latitud');
$table->string('longitud'); $table->string('longitud');
@@ -21,9 +20,6 @@ return new class extends Migration
}); });
} }
/**
* Reverse the migrations.
*/
public function down(): void public function down(): void
{ {
Schema::dropIfExists('codes'); Schema::dropIfExists('codes');
-3
View File
@@ -10,9 +10,6 @@ class DatabaseSeeder extends Seeder
{ {
use WithoutModelEvents; use WithoutModelEvents;
/**
* Seed the application's database.
*/
public function run(): void public function run(): void
{ {
$this->call(RoleSeeder::class); $this->call(RoleSeeder::class);
+29 -1
View File
@@ -3,7 +3,6 @@
namespace Database\Seeders; namespace Database\Seeders;
use App\Models\Plantel; use App\Models\Plantel;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
class PlantelSeeder extends Seeder class PlantelSeeder extends Seeder
@@ -18,6 +17,9 @@ class PlantelSeeder extends Seeder
'razon'=>'123456789', 'razon'=>'123456789',
'ubicacion'=>'Carretera Aculco-Amealco S/N, 50360 Aculco de Espinoza, Méx.', 'ubicacion'=>'Carretera Aculco-Amealco S/N, 50360 Aculco de Espinoza, Méx.',
'telefono'=>7181240287, 'telefono'=>7181240287,
'nomenclatura'=>'ACU',
'latitud'=>'20.095675599841005',
'longitud'=>'-99.83370646050189',
'status'=>1 'status'=>1
]); ]);
@@ -26,6 +28,9 @@ class PlantelSeeder extends Seeder
'razon'=>'123456789', 'razon'=>'123456789',
'ubicacion'=>'5 de Mayo 26, Centro, 76800 San Juan del Río, Qro.', 'ubicacion'=>'5 de Mayo 26, Centro, 76800 San Juan del Río, Qro.',
'telefono'=>4272774826, 'telefono'=>4272774826,
'nomenclatura'=>'SJR',
'latitud'=>'20.38996649108517,20.384703934068455',
'longitud'=>'-99.99204858595175,-99.99729277268392',
'status'=>1 'status'=>1
]); ]);
@@ -34,6 +39,9 @@ class PlantelSeeder extends Seeder
'razon'=>'123456789', 'razon'=>'123456789',
'ubicacion'=>'Carr. México - Pachuca Km. 38.5, Tecámac Centro, Tecamac, 55740 Tecámac de Felipe Villanueva, Méx.', 'ubicacion'=>'Carr. México - Pachuca Km. 38.5, Tecámac Centro, Tecamac, 55740 Tecámac de Felipe Villanueva, Méx.',
'telefono'=>5533552116, 'telefono'=>5533552116,
'nomenclatura'=>'TEC',
'latitud'=>'19.71385878202753',
'longitud'=>'-98.97363138918948',
'status'=>1 'status'=>1
]); ]);
@@ -42,6 +50,9 @@ class PlantelSeeder extends Seeder
'razon'=>'123456789', 'razon'=>'123456789',
'ubicacion'=>'C. Nigromante #85, San Francisco 1ra, 42854 Tepeji del Río de Ocampo, Hgo.', 'ubicacion'=>'C. Nigromante #85, San Francisco 1ra, 42854 Tepeji del Río de Ocampo, Hgo.',
'telefono'=>7736826093, 'telefono'=>7736826093,
'nomenclatura'=>'TEP',
'latitud'=>'19.89967939304829',
'longitud'=>'-99.34733192966145',
'status'=>1 'status'=>1
]); ]);
@@ -50,6 +61,9 @@ class PlantelSeeder extends Seeder
'razon'=>'123456789', 'razon'=>'123456789',
'ubicacion'=>'Legislatura 56, 50450 Atlacomulco de Fabela, Méx.', 'ubicacion'=>'Legislatura 56, 50450 Atlacomulco de Fabela, Méx.',
'telefono'=>7122174727, 'telefono'=>7122174727,
'nomenclatura'=>'ATL',
'latitud'=>'19.79552485274741',
'longitud'=>'-99.87546357384377',
'status'=>1 'status'=>1
]); ]);
@@ -58,6 +72,20 @@ class PlantelSeeder extends Seeder
'razon'=>'123456789', 'razon'=>'123456789',
'ubicacion'=>'Prof. Benjamín García Uribe 8, Centro, 42470 Nopala de Villagrán, Hgo.', 'ubicacion'=>'Prof. Benjamín García Uribe 8, Centro, 42470 Nopala de Villagrán, Hgo.',
'telefono'=>7731328198, 'telefono'=>7731328198,
'nomenclatura'=>'NOP',
'latitud'=>'20.251432945100376',
'longitud'=>'-99.64692423068954',
'status'=>1
]);
Plantel::create([
'name'=>'En línea',
'razon'=>'123456789',
'ubicacion'=>'',
'telefono'=>7731328198,
'nomenclatura'=>'LIN',
'latitud'=>'19.71385878202753',
'longitud'=>'-98.97363138918948',
'status'=>1 'status'=>1
]); ]);
} }
+1
View File
@@ -31,6 +31,7 @@ class UserSeeder extends Seeder
'direccion'=>'Zacatenco mz 21 lt 2 san francisco cuautliquixca', 'direccion'=>'Zacatenco mz 21 lt 2 san francisco cuautliquixca',
'telefono'=>'5636287758', 'telefono'=>'5636287758',
'password' => bcrypt('2000'), 'password' => bcrypt('2000'),
'code'=>1,
'status'=>1 'status'=>1
])->assignRole('Promotor'); ])->assignRole('Promotor');
$user2->plantelUsuarios()->sync([1,2,3]); $user2->plantelUsuarios()->sync([1,2,3]);
+25 -16
View File
@@ -24,25 +24,37 @@ function iniciarScanner(){
scanner = new Html5Qrcode("reader"); scanner = new Html5Qrcode("reader");
scanner.start( scanner.start(
{ facingMode: "environment" }, { facingMode: "environment" },
{ fps: 10, qrbox: 250 }, { fps: 10, qrbox: 250 },
(mensaje) => { (mensaje) => {
if(!gpsListo){
Swal.fire("GPS","Esperando ubicación...","warning");
return;
}
if(yaEscaneado) return;
yaEscaneado = true;
Livewire.dispatch('qrLeido', {
texto: mensaje,
lat: latitud,
lng: longitud
});
if(!gpsListo){
Swal.fire("GPS","Esperando ubicación...","warning");
return;
} }
).catch(err => {
if(yaEscaneado) return; console.error("Error cámara:", err);
yaEscaneado = true;
Livewire.dispatch('qrLeido', { Swal.fire(
texto: mensaje, "Cámara bloqueada",
lat: latitud, "Debes permitir el acceso a la cámara para escanear el QR",
lng: longitud "error"
}); );
}); });
} }
document.addEventListener('livewire:init', () => { document.addEventListener('livewire:init', () => {
@@ -97,7 +109,4 @@ document.addEventListener("visibilitychange", function() {
</script> </script>
@endpush @endpush
+16
View File
@@ -28,6 +28,22 @@
<label class="small mb-1">Ubicación</label> <label class="small mb-1">Ubicación</label>
<input class="form-control" name="ubicacion"> <input class="form-control" name="ubicacion">
</div> </div>
<div class="mb-3">
<label class="small mb-1">Nomenclatura</label>
<input class="form-control" name="nomenclatura">
</div>
<div class="mb-3">
<label class="small mb-1">Latitúd</label>
<input class="form-control" name="latitud">
</div>
<div class="mb-3">
<label class="small mb-1">Longitud</label>
<input class="form-control" name="longitud">
</div>
<div class="mb-3"> <div class="mb-3">
<label class="small mb-1">Teléfono</label> <label class="small mb-1">Teléfono</label>
<input class="form-control" name="telefono" type="number"> <input class="form-control" name="telefono" type="number">
+15
View File
@@ -34,6 +34,21 @@
<label class="small mb-1">Ubicación</label> <label class="small mb-1">Ubicación</label>
<input class="form-control" name="ubicacion" value="{{ $plantel->ubicacion }}"> <input class="form-control" name="ubicacion" value="{{ $plantel->ubicacion }}">
</div> </div>
<div class="mb-3">
<label class="small mb-1">Nomenclatura</label>
<input class="form-control" name="nomenclatura" value="{{ $plantel->nomenclatura }}">
</div>
<div class="mb-3">
<label class="small mb-1">Latitúd</label>
<input class="form-control" name="latitud" value="{{ $plantel->latitud }}">
</div>
<div class="mb-3">
<label class="small mb-1">Longitud</label>
<input class="form-control" name="longitud" value="{{ $plantel->longitud }}">
</div>
<div class="mb-3"> <div class="mb-3">
<label class="small mb-1">Teléfono</label> <label class="small mb-1">Teléfono</label>
<input class="form-control" name="telefono" type="number" value="{{ $plantel->telefono }}"> <input class="form-control" name="telefono" type="number" value="{{ $plantel->telefono }}">
+10
View File
@@ -72,6 +72,16 @@
</div> </div>
<div class="mb-3">
<label for="status">Seleccionar código</label>
<select class="form-control" name="codigo" id="codigo">
<option value="0">Selecciona un código....</option>
@foreach ($codigos as $codigo)
<option value="{{ $codigo->id }}">{{ $codigo->name}}</option>
@endforeach
</select>
</div>
<div class="mb-3"> <div class="mb-3">
<label for="status">Seleccionar status</label> <label for="status">Seleccionar status</label>
<select class="form-control" name="status" id="status"> <select class="form-control" name="status" id="status">
+17 -6
View File
@@ -42,7 +42,7 @@
<input class="form-control" name="direccion" value="{{$user->direccion}}"> <input class="form-control" name="direccion" value="{{$user->direccion}}">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="plantels[]">Seleccionar campus</label> <label for="plantels[]">Seleccionar campus</label>
@foreach ($plantels as $plantel) @foreach ($plantels as $plantel)
@@ -56,10 +56,10 @@
{{ $plantel->name }} {{ $plantel->name }}
</label> </label>
</div> </div>
@endforeach @endforeach
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="small mb-1" for="roles[]">Roles</label> <label class="small mb-1" for="roles[]">Roles</label>
@foreach ($roles as $role) @foreach ($roles as $role)
<div class="form-check"> <div class="form-check">
@@ -71,16 +71,27 @@
{{ $role->name }} {{ $role->name }}
</label> </label>
</div> </div>
@endforeach @endforeach
</div> </div>
<div class="mb-3">
<label for="status">Seleccionar código</label>
<select class="form-control" name="code" id="code" value="{{ $user->code }}">
<option value="0">Selecciona un código....</option>
@foreach ($codigos as $codigo)
<option value="{{ $codigo->id }}" {{ $user->code == $codigo->id ? 'selected' : '' }}>{{ $codigo->name}}</option>
@endforeach
</select>
</div>
<div class="mb-3"> <div class="mb-3">
<label for="status">Seleccionar status</label> <label for="status">Seleccionar status</label>
<select class="form-control" name="status" id="status" value="{{ $user->status }}"> <select class="form-control" name="status" id="status" value="{{ $user->status }}">
<option value="1" {{ $user->status=='1' ? 'selected' : '' }}>Activo</option> <option value="1" {{ $user->status=='1' ? 'selected' : '' }}>Activo</option>
<option value="0" {{ $user->status=='0' ? 'selected' : '' }}>Inactivo</option> <option value="0" {{ $user->status=='0' ? 'selected' : '' }}>Inactivo</option>
</select> </select>
</div> </div>
<input class="btn btn-primary" type="submit" value="Update"> <input class="btn btn-primary" type="submit" value="Update">
</form> </form>