From a52be91dd44873873993cbddc3444fe4f5ed6f89 Mon Sep 17 00:00:00 2001 From: javier Date: Thu, 25 Jun 2026 16:19:33 +0200 Subject: [PATCH] =?UTF-8?q?feat(api):=20sincronizar=20tipo=20de=20elemento?= =?UTF-8?q?=20y=20activo/inactivo=20en=20la=20app=20m=C3=B3vil?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - bundle: mapFeature incluye feature_type_id e is_active; nuevo array feature_types (catálogo) para que el móvil resuelva nombres/colores. - /sync feature.update acepta is_active y feature_type_id (last-write-wins). - openapi.yaml: bundle documenta feature_types. Tests: MobileApiTest cubre feature.update con los nuevos campos + bundle. Suite 88 passing. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Api/V1/ProjectApiController.php | 2 ++ .../Controllers/Api/V1/SyncController.php | 10 ++++---- docs/openapi.yaml | 1 + tests/Feature/Api/MobileApiTest.php | 24 +++++++++++++++++++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Api/V1/ProjectApiController.php b/app/Http/Controllers/Api/V1/ProjectApiController.php index bc1490d..1d54621 100644 --- a/app/Http/Controllers/Api/V1/ProjectApiController.php +++ b/app/Http/Controllers/Api/V1/ProjectApiController.php @@ -73,6 +73,7 @@ class ProjectApiController extends Controller 'phases' => $phases->map(fn ($p) => $this->mapPhase($p))->values(), 'layers' => $layers->map(fn ($l) => $this->mapLayer($l))->values(), 'features' => $features->map(fn ($f) => $this->mapFeature($f))->values(), + 'feature_types' => \App\Models\FeatureType::orderBy('name')->get(['id', 'name', 'color'])->values(), 'inspections' => $inspections->map(fn ($i) => $this->mapInspection($i))->values(), 'issues' => $issues->map(fn ($i) => $this->mapIssue($i))->values(), 'issue_tasks' => $issueTasks->map(fn ($t) => $this->mapIssueTask($t))->values(), @@ -156,6 +157,7 @@ class ProjectApiController extends Controller 'id' => $f->id, 'layer_id' => $f->layer_id, 'name' => $f->name, 'geometry' => $f->geometry, 'status' => $f->status, 'progress' => $f->progress, 'responsible' => $f->responsible, 'template_id' => $f->template_id, + 'feature_type_id' => $f->feature_type_id, 'is_active' => (bool) $f->is_active, 'updated_at' => $f->updated_at?->toIso8601String(), ]; } diff --git a/app/Http/Controllers/Api/V1/SyncController.php b/app/Http/Controllers/Api/V1/SyncController.php index 012b328..cf1b7a3 100644 --- a/app/Http/Controllers/Api/V1/SyncController.php +++ b/app/Http/Controllers/Api/V1/SyncController.php @@ -368,10 +368,12 @@ class SyncController extends Controller private function featureUpdate(User $user, string $uuid, array $op): array { $v = Validator::make($op['data'], [ - 'id' => ['required', 'integer', 'exists:features,id'], - 'status' => ['nullable', 'string'], - 'progress' => ['nullable', 'integer', 'min:0', 'max:100'], - 'responsible' => ['nullable', 'string'], + 'id' => ['required', 'integer', 'exists:features,id'], + 'status' => ['nullable', 'string'], + 'progress' => ['nullable', 'integer', 'min:0', 'max:100'], + 'responsible' => ['nullable', 'string'], + 'is_active' => ['nullable', 'boolean'], + 'feature_type_id' => ['nullable', 'integer', 'exists:feature_types,id'], ]); if ($v->fails()) { return $this->error($uuid, 'validation: ' . $v->errors()->first()); diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 30e168f..08ebfc7 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -183,6 +183,7 @@ components: phases: { type: array, items: { type: object } } layers: { type: array, items: { type: object } } features: { type: array, items: { type: object } } + feature_types: { type: array, items: { type: object } } inspections: { type: array, items: { type: object } } issues: { type: array, items: { type: object } } issue_tasks: { type: array, items: { type: object } } diff --git a/tests/Feature/Api/MobileApiTest.php b/tests/Feature/Api/MobileApiTest.php index a23b541..4b30619 100644 --- a/tests/Feature/Api/MobileApiTest.php +++ b/tests/Feature/Api/MobileApiTest.php @@ -356,6 +356,30 @@ class MobileApiTest extends TestCase $this->assertEquals(100, $phase->fresh()->progress_percent); } + public function test_feature_update_sets_active_and_type_and_bundle_exposes_them(): void + { + $user = User::factory()->create(); + $user->givePermissionTo('update progress'); + $project = $this->makeProject($user); + $feature = $this->makeFeature($this->makeLayer($this->makePhase($project))); + $type = \App\Models\FeatureType::create(['name' => 'Pilar', 'color' => '#ffffff']); + + Sanctum::actingAs($user, ['mobile-sync']); + $this->postJson('/api/v1/sync', ['operations' => [[ + 'entity' => 'feature', 'op' => 'update', 'uuid' => (string) Str::uuid(), + 'data' => ['id' => $feature->id, 'is_active' => false, 'feature_type_id' => $type->id], + ]]])->assertOk()->assertJsonPath('results.0.status', 'applied'); + + $feature->refresh(); + $this->assertFalse($feature->is_active); + $this->assertEquals($type->id, $feature->feature_type_id); + + $res = $this->getJson("/api/v1/projects/{$project->id}/bundle")->assertOk(); + $this->assertEquals($type->id, $res->json('features.0.feature_type_id')); + $this->assertFalse($res->json('features.0.is_active')); + $this->assertTrue(collect($res->json('feature_types'))->pluck('id')->contains($type->id)); + } + public function test_sync_returns_conflict_when_server_is_newer(): void { $user = User::factory()->create();