From 5e3b203f846591a1d05361bc53b530ce7820a79b Mon Sep 17 00:00:00 2001 From: CarlosTorres Date: Sun, 18 Jan 2026 23:16:34 +0000 Subject: [PATCH] Fix: Correcciones panel admin y API frontend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Panel de Administración ### FormBuilder personalizado (Laravel 11) - Creado app/Support/FormBuilder.php como reemplazo de laravelcollective/html - Creado app/Support/Facades/Form.php para el facade - Registrado en AppServiceProvider y config/app.php - Soporta: text, email, password, file, textarea, select, checkbox, radio, etc. - Manejo de valores null en todos los parámetros ### Configuración de sesión - Cambiado same_site de "none" a "lax" para compatibilidad HTTP - Corrige error 419 Page Expired en login ### Status CRUD - Agregado campo en_name al formulario (español/inglés) - Actualizado StatusController create/update para manejar en_name ### Dependencias - Instalado spatie/laravel-google-cloud-storage para driver GCS ## API Frontend ### Validaciones de perfil de proveedor Agregadas validaciones en endpoints que requieren perfil de proveedor: - SupplierController::getpostulation - Retorna error 400 si no hay perfil - SupplierController::getcontractedpostulation - Retorna error 400 si no hay perfil - PostulationController::postulate - Retorna error 400 si no hay perfil - PostulationController::getfinishedpostulations - Retorna error 400 si no hay perfil - ContractController::startcontract - Retorna error 400 si no hay perfil ### Null safety en contratos - ContractController::getcurrentcontracts - Manejo seguro de supplier/category null - ContractController::getfinishedcontracts - Manejo seguro de supplier/category/status null Co-Authored-By: Claude Opus 4.5 --- app/Http/Controllers/ContractController.php | 24 +- .../Controllers/PostulationController.php | 15 + app/Http/Controllers/StatusController.php | 6 + app/Http/Controllers/SupplierController.php | 16 ++ app/Providers/AppServiceProvider.php | 5 +- app/Support/Facades/Form.php | 13 + app/Support/FormBuilder.php | 265 ++++++++++++++++++ composer.json | 3 +- composer.lock | 123 +++++++- config/app.php | 1 + config/session.php | 2 +- resources/views/status/form.blade.php | 9 +- 12 files changed, 469 insertions(+), 13 deletions(-) create mode 100644 app/Support/Facades/Form.php create mode 100644 app/Support/FormBuilder.php diff --git a/app/Http/Controllers/ContractController.php b/app/Http/Controllers/ContractController.php index 88451d1..154255d 100755 --- a/app/Http/Controllers/ContractController.php +++ b/app/Http/Controllers/ContractController.php @@ -639,12 +639,12 @@ class ContractController extends Controller $day_limit = Carbon::parse($ccontract->created_at); $currentcontractinfo = array( 'id' => $ccontract->id, - 'phone' => $supplier->user->phone, - 'category' => $category->name, - 'en_category' => $category->en_name, + 'phone' => $supplier ? ($supplier->user ? $supplier->user->phone : null) : null, + 'category' => $category ? $category->name : null, + 'en_category' => $category ? $category->en_name : null, 'address' => $ccontract->address, 'date' => $ccontract->appointment, - 'supplier' => $supplier->company_name, + 'supplier' => $supplier ? $supplier->company_name : 'Proveedor no disponible', 'status' => $ccontract->status_id, 'amount' => $ccontract->amount, 'code' => $ccontract->code, @@ -778,6 +778,14 @@ class ContractController extends Controller $user = Auth::user(); $supplier = $user->suppliers; + + if (!$supplier) { + return response()->json([ + 'success' => false, + 'message' => 'No tienes un perfil de proveedor registrado' + ], 400); + } + $ccontract = CurrentContracts::where('code', $request->contract_pin)->where('supplier_id', $supplier->id)->first(); if($ccontract) { @@ -969,16 +977,16 @@ class ContractController extends Controller $day_limit = Carbon::parse($fcontract->created_at); $finishedcontractinfo = array( 'id' => $fcontract->id, - 'category' => $category->name, - 'en_category' => $category->en_name, + 'category' => $category ? $category->name : null, + 'en_category' => $category ? $category->en_name : null, 'address' => $fcontract->address, 'date' => $fcontract->appointment, 'date_difference' => $time_limit->diff(Carbon::now(), false)->days, - 'supplier' => $supplier->company_name, + 'supplier' => $supplier ? $supplier->company_name : 'Proveedor no disponible', 'amount' => $fcontract->amount, 'scored' => $fcontract->scored_at, 'parent' => $fcontract->parent_contract_id, - 'status' => $fcontract->status->name + 'status' => $fcontract->status ? $fcontract->status->name : null ); $finishedcontracts[] = $finishedcontractinfo; } diff --git a/app/Http/Controllers/PostulationController.php b/app/Http/Controllers/PostulationController.php index 668047c..ccdd307 100755 --- a/app/Http/Controllers/PostulationController.php +++ b/app/Http/Controllers/PostulationController.php @@ -218,6 +218,13 @@ class PostulationController extends Controller $time_limit = (9900 - Carbon::now()->diffInMinutes($time_created)); $supplier = Suppliers::where('user_id', $user->id)->first(); + if (!$supplier) { + return response()->json([ + 'success' => false, + 'message' => 'No tienes un perfil de proveedor registrado' + ], 400); + } + if ($time_limit > 0) { if (in_array($postulation->category_id, $supplier->categories->pluck('id')->toArray())) { /*if($supplier->membership == 1) { @@ -282,6 +289,14 @@ class PostulationController extends Controller public function getfinishedpostulations(Request $request) { $user = Auth::user(); + + if (!$user->suppliers) { + return response()->json([ + 'success' => false, + 'message' => 'No tienes un perfil de proveedor registrado' + ], 400); + } + $postulations = FinishedContracts::where('supplier_id', $user->suppliers->id)->orderBy('created_at', 'DESC')->get(); $finishedpostulations = array(); diff --git a/app/Http/Controllers/StatusController.php b/app/Http/Controllers/StatusController.php index b0ea976..7eeb289 100755 --- a/app/Http/Controllers/StatusController.php +++ b/app/Http/Controllers/StatusController.php @@ -53,10 +53,12 @@ class StatusController extends Controller $rules = [ 'name' => 'required|string', + 'en_name' => 'required|string', ]; $messages = [ 'name.required' => 'Se requiere el nombre del status', + 'en_name.required' => 'Se requiere el nombre del status en inglés', ]; $validator = Validator::make($request->all(), $rules, $messages); @@ -66,6 +68,7 @@ class StatusController extends Controller $status = new Status(); $status->name = strip_tags($request->name); + $status->en_name = strip_tags($request->en_name); $status->save(); return redirect('status'); @@ -119,10 +122,12 @@ class StatusController extends Controller $rules = [ 'name' => 'required|string', + 'en_name' => 'required|string', ]; $messages = [ 'name.required' => 'Se requiere el nombre del status', + 'en_name.required' => 'Se requiere el nombre del status en inglés', ]; $validator = Validator::make($request->all(), $rules, $messages); @@ -132,6 +137,7 @@ class StatusController extends Controller $status = Status::find($id); $status->name = strip_tags($request->name); + $status->en_name = strip_tags($request->en_name); $status->save(); return redirect('status'); diff --git a/app/Http/Controllers/SupplierController.php b/app/Http/Controllers/SupplierController.php index 1760e8a..f8ae52c 100755 --- a/app/Http/Controllers/SupplierController.php +++ b/app/Http/Controllers/SupplierController.php @@ -1211,6 +1211,14 @@ class SupplierController extends Controller $user = Auth::user(); $supplier = Suppliers::where('user_id', $user->id)->first(); + + if (!$supplier) { + return response()->json([ + 'success' => false, + 'message' => 'No tienes un perfil de proveedor registrado' + ], 400); + } + $distance = 0.5; $postulations = Postulations::distance('location', $supplier->location, $distance)->orderBy('created_at', 'DESC')->get(); @@ -1246,6 +1254,14 @@ class SupplierController extends Controller $user = Auth::user(); $supplier = Suppliers::where('user_id', $user->id)->first(); + + if (!$supplier) { + return response()->json([ + 'success' => false, + 'message' => 'No tienes un perfil de proveedor registrado' + ], 400); + } + $contracts = CurrentContracts::where('supplier_id', $supplier->id)->orderBy('created_at', 'DESC')->get(); $contractsinfo = array(); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index f1ff3c4..70547d7 100755 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -3,6 +3,7 @@ namespace App\Providers; use App\Services\SocialUserResolver; +use App\Support\FormBuilder; use Coderello\SocialGrant\Resolvers\SocialUserResolverInterface; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\Schema; @@ -25,7 +26,9 @@ class AppServiceProvider extends ServiceProvider */ public function register() { - // + $this->app->singleton('form', function ($app) { + return new FormBuilder(); + }); } /** diff --git a/app/Support/Facades/Form.php b/app/Support/Facades/Form.php new file mode 100644 index 0000000..cf3023c --- /dev/null +++ b/app/Support/Facades/Form.php @@ -0,0 +1,13 @@ +model = $model; + return $this->open($options ?? []); + } + + public function open($options = []) + { + $options = $options ?? []; + $method = strtoupper($options['method'] ?? 'POST'); + $action = $options['url'] ?? ($options['action'] ?? ''); + $files = isset($options['files']) && $options['files'] ? ' enctype="multipart/form-data"' : ''; + $class = isset($options['class']) ? ' class="' . e($options['class']) . '"' : ''; + $id = isset($options['id']) ? ' id="' . e($options['id']) . '"' : ''; + + $html = '
'; + $html .= csrf_field(); + + if (!in_array($method, ['GET', 'POST'])) { + $html .= method_field($method); + } + + return new HtmlString($html); + } + + public function close() + { + $this->model = null; + return new HtmlString('
'); + } + + public function token() + { + return csrf_field(); + } + + public function label($name, $value = null, $options = [], $escapeHtml = true) + { + $options = $options ?? []; + $value = $value ?? ucfirst(str_replace('_', ' ', $name)); + $for = ' for="' . e($name) . '"'; + $attributes = $this->attributes($options); + + return new HtmlString('' . ($escapeHtml ? e($value) : $value) . ''); + } + + public function text($name, $value = null, $options = []) + { + return $this->input('text', $name, $value, $options ?? []); + } + + public function email($name, $value = null, $options = []) + { + return $this->input('email', $name, $value, $options ?? []); + } + + public function password($name, $options = []) + { + return $this->input('password', $name, null, $options ?? []); + } + + public function hidden($name, $value = null, $options = []) + { + return $this->input('hidden', $name, $value, $options ?? []); + } + + public function number($name, $value = null, $options = []) + { + return $this->input('number', $name, $value, $options ?? []); + } + + public function file($name, $options = []) + { + return $this->input('file', $name, null, $options ?? []); + } + + public function textarea($name, $value = null, $options = []) + { + $options = $options ?? []; + $value = $this->getValueAttribute($name, $value); + $attributes = $this->attributes(array_merge(['name' => $name, 'id' => $name], $options)); + + return new HtmlString('' . e($value ?? '') . ''); + } + + public function select($name, $list = [], $selected = null, $options = [], $optionsAttributes = [], $optgroupsAttributes = []) + { + $options = $options ?? []; + $list = $list ?? []; + $selected = $this->getValueAttribute($name, $selected); + $attributes = $this->attributes(array_merge(['name' => $name, 'id' => $name], $options)); + + $html = ''; + + foreach ($list as $key => $value) { + if (is_array($value)) { + $html .= ''; + foreach ($value as $optKey => $optValue) { + $html .= $this->option($optKey, $optValue, $selected); + } + $html .= ''; + } else { + $html .= $this->option($key, $value, $selected); + } + } + + $html .= ''; + + return new HtmlString($html); + } + + protected function option($key, $value, $selected) + { + $isSelected = $this->isSelected($key, $selected) ? ' selected' : ''; + return ''; + } + + protected function isSelected($key, $selected) + { + if (is_array($selected)) { + return in_array($key, $selected); + } + return (string) $key === (string) $selected; + } + + public function checkbox($name, $value = 1, $checked = null, $options = []) + { + $options = $options ?? []; + $checked = $this->getCheckedState($name, $value, $checked) ? ' checked' : ''; + $attributes = $this->attributes(array_merge([ + 'name' => $name, + 'id' => $options['id'] ?? $name, + 'value' => $value, + 'type' => 'checkbox' + ], $options)); + + return new HtmlString(''); + } + + public function radio($name, $value = null, $checked = null, $options = []) + { + $options = $options ?? []; + $checked = $this->getCheckedState($name, $value, $checked) ? ' checked' : ''; + $attributes = $this->attributes(array_merge([ + 'name' => $name, + 'id' => $options['id'] ?? $name . '_' . $value, + 'value' => $value, + 'type' => 'radio' + ], $options)); + + return new HtmlString(''); + } + + public function submit($value = null, $options = []) + { + return $this->input('submit', null, $value, $options ?? []); + } + + public function button($value = null, $options = []) + { + $options = $options ?? []; + $attributes = $this->attributes(array_merge(['type' => 'button'], $options)); + return new HtmlString('' . e($value ?? '') . ''); + } + + public function input($type, $name, $value = null, $options = []) + { + $options = $options ?? []; + + if ($type !== 'password' && $type !== 'file') { + $value = $this->getValueAttribute($name, $value); + } + + $attributes = $this->attributes(array_merge([ + 'type' => $type, + 'name' => $name, + 'id' => $options['id'] ?? $name, + 'value' => $value + ], $options)); + + return new HtmlString(''); + } + + public function date($name, $value = null, $options = []) + { + return $this->input('date', $name, $value, $options ?? []); + } + + public function time($name, $value = null, $options = []) + { + return $this->input('time', $name, $value, $options ?? []); + } + + public function datetime($name, $value = null, $options = []) + { + return $this->input('datetime-local', $name, $value, $options ?? []); + } + + protected function getValueAttribute($name, $value = null) + { + if (is_null($name)) { + return $value; + } + + $old = old($name); + if (!is_null($old)) { + return $old; + } + + if (!is_null($value)) { + return $value; + } + + if ($this->model && isset($this->model->{$name})) { + return $this->model->{$name}; + } + + return null; + } + + protected function getCheckedState($name, $value, $checked) + { + $old = old($name); + if (!is_null($old)) { + return $old == $value; + } + + if (!is_null($checked)) { + return $checked; + } + + if ($this->model && isset($this->model->{$name})) { + return $this->model->{$name} == $value; + } + + return false; + } + + protected function attributes($attributes) + { + $attributes = $attributes ?? []; + $html = ''; + foreach ($attributes as $key => $value) { + if (is_null($value)) { + continue; + } + if (is_numeric($key)) { + $html .= ' ' . $value; + } else { + $html .= ' ' . $key . '="' . e($value) . '"'; + } + } + return $html; + } +} diff --git a/composer.json b/composer.json index 854a034..bb34ee5 100755 --- a/composer.json +++ b/composer.json @@ -20,11 +20,12 @@ "laravel/socialite": "^5.10", "laravel/tinker": "^2.9", "laravel/ui": "^4.6", - "spatie/laravel-html": "^3.0", "lcobucci/jwt": "^5.0", "mercadopago/dx-php": "^3.5", "missael-anda/laravel-whatsapp": "^0.8.6", "openpay/sdk": "^2.0", + "spatie/laravel-google-cloud-storage": "^2.3", + "spatie/laravel-html": "^3.0", "tarfin-labs/laravel-spatial": "*", "timehunter/laravel-google-recaptcha-v3": "^2.4" }, diff --git a/composer.lock b/composer.lock index d5d4495..50594ee 100755 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bff602eb6c826efc24c4833d959d4c90", + "content-hash": "5e417628f1993b1a32e8bdd7a16566de", "packages": [ { "name": "berkayk/onesignal-laravel", @@ -3014,6 +3014,54 @@ }, "time": "2025-11-10T17:13:11+00:00" }, + { + "name": "league/flysystem-google-cloud-storage", + "version": "3.30.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-google-cloud-storage.git", + "reference": "2d36f1a050fe70bf21d8aa75275963f9ca2e16ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-google-cloud-storage/zipball/2d36f1a050fe70bf21d8aa75275963f9ca2e16ea", + "reference": "2d36f1a050fe70bf21d8aa75275963f9ca2e16ea", + "shasum": "" + }, + "require": { + "google/cloud-storage": "^1.23", + "league/flysystem": "^3.10.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\GoogleCloudStorage\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Google Cloud Storage adapter for Flysystem.", + "keywords": [ + "Flysystem", + "filesystem", + "gcs", + "google cloud storage" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-google-cloud-storage/tree/3.30.1" + }, + "time": "2025-10-20T15:27:33+00:00" + }, { "name": "league/flysystem-local", "version": "3.30.2", @@ -5430,6 +5478,79 @@ ], "time": "2025-12-02T15:19:04+00:00" }, + { + "name": "spatie/laravel-google-cloud-storage", + "version": "2.3.4", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-google-cloud-storage.git", + "reference": "10c91e6dcaebf83eba9f21b8107267595bb40d2a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-google-cloud-storage/zipball/10c91e6dcaebf83eba9f21b8107267595bb40d2a", + "reference": "10c91e6dcaebf83eba9f21b8107267595bb40d2a", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^10.0|^11.0|^12.0", + "illuminate/filesystem": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", + "league/flysystem-google-cloud-storage": "^3.0.15", + "php": "^8.0" + }, + "require-dev": { + "nunomaduro/collision": "^7.0|^8.0", + "orchestra/testbench": "^8.0|^9.0|^10.0", + "phpunit/phpunit": "^10.0|^11.0|^12.0", + "spatie/laravel-ray": "^1.29" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "GoogleCloudStorage": "Spatie\\GoogleCloudStorage\\GoogleCloudStorageFacade" + }, + "providers": [ + "Spatie\\GoogleCloudStorage\\GoogleCloudStorageServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\GoogleCloudStorage\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Vanderbist", + "email": "alex@spatie.be", + "role": "Developer" + } + ], + "description": "Google Cloud Storage filesystem driver for Laravel", + "homepage": "https://github.com/spatie/laravel-google-cloud-storage", + "keywords": [ + "laravel", + "laravel-google-cloud-storage", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-google-cloud-storage/issues", + "source": "https://github.com/spatie/laravel-google-cloud-storage/tree/2.3.4" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-03-13T09:56:26+00:00" + }, { "name": "spatie/laravel-html", "version": "3.12.3", diff --git a/config/app.php b/config/app.php index 3eec28b..0cec95d 100755 --- a/config/app.php +++ b/config/app.php @@ -215,6 +215,7 @@ return [ 'Eloquent' => Illuminate\Database\Eloquent\Model::class, 'Event' => Illuminate\Support\Facades\Event::class, 'File' => Illuminate\Support\Facades\File::class, + 'Form' => App\Support\Facades\Form::class, 'Gate' => Illuminate\Support\Facades\Gate::class, 'Hash' => Illuminate\Support\Facades\Hash::class, 'Image' => Intervention\Image\Laravel\Facades\Image::class, diff --git a/config/session.php b/config/session.php index 6c7a1d6..efa74c7 100755 --- a/config/session.php +++ b/config/session.php @@ -194,6 +194,6 @@ return [ | */ - 'same_site' => "none", + 'same_site' => env('SESSION_SAME_SITE', 'lax'), ]; diff --git a/resources/views/status/form.blade.php b/resources/views/status/form.blade.php index d5f70c0..ab1be2e 100755 --- a/resources/views/status/form.blade.php +++ b/resources/views/status/form.blade.php @@ -11,12 +11,19 @@ {!! Form::open(['id'=>'frm']) !!} @endif
- {!! Form::label("name","Status",["class"=>"col-form-label col-md-3 col-lg-2"]) !!} + {!! Form::label("name","Status (Español)",["class"=>"col-form-label col-md-3 col-lg-2"]) !!}
{!! Form::text("name",null,["class"=>"form-control".($errors->has('name')?" is-invalid":""),"autofocus",'placeholder'=>'Nombre del Status']) !!}
+
+ {!! Form::label("en_name","Status (English)",["class"=>"col-form-label col-md-3 col-lg-2"]) !!} +
+ {!! Form::text("en_name",null,["class"=>"form-control".($errors->has('en_name')?" is-invalid":""),'placeholder'=>'Status Name']) !!} + +
+
@if ($errors->any())