feat(api): sincronizar tipo de elemento y activo/inactivo en la app móvil

- 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) <noreply@anthropic.com>
This commit is contained in:
2026-06-25 16:19:33 +02:00
co-authored by Claude Opus 4.8
parent 3ee70cf48d
commit a52be91dd4
4 changed files with 33 additions and 4 deletions
@@ -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(),
];
}
@@ -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());
+1
View File
@@ -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 } }
+24
View File
@@ -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();