feat(phases): modal crear/editar fase + tabla Rappasoft

- PhaseList pasa a contenedor: botón "Agregar fase" + modal crear/editar con todos
  los parámetros (nombre, descripción, orden, color, progreso, fechas previstas y
  reales) y validación. Antes "Agregar fase" creaba directamente 'Nueva fase'.
- PhaseTable (Rappasoft): orden, nombre+descripción, barra de progreso, fechas,
  color y acciones (editar abre el modal vía evento, actualizar progreso, eliminar);
  búsqueda y ordenación. Gateado por 'manage phases' + acceso al proyecto.

Tests: PhaseManagementTest (4). Suite 65 passing (solo 2 pre-existentes sqlite).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-18 13:56:05 +02:00
parent 3d0f4d5cad
commit c378ab5884
4 changed files with 464 additions and 42 deletions
+115
View File
@@ -0,0 +1,115 @@
<?php
namespace Tests\Feature;
use App\Livewire\PhaseList;
use App\Livewire\PhaseTable;
use App\Models\Phase;
use App\Models\Project;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Tests\TestCase;
class PhaseManagementTest extends TestCase
{
use RefreshDatabase;
private User $user;
private Project $project;
protected function setUp(): void
{
parent::setUp();
foreach (['manage phases', 'edit projects'] as $p) {
Permission::findOrCreate($p);
}
$this->user = User::factory()->create();
$this->user->givePermissionTo(['manage phases', 'edit projects']);
$this->project = Project::create([
'reference' => 'PH-1',
'name' => 'Proyecto Fases',
'address' => 'Calle 1',
'lat' => 40.0,
'lng' => -3.0,
'start_date' => now()->toDateString(),
'end_date_estimated' => now()->addMonths(3)->toDateString(),
'status' => 'in_progress',
'created_by' => $this->user->id,
]);
$this->project->users()->attach($this->user->id, ['role_in_project' => 'supervisor']);
}
public function test_create_phase_via_modal_persists_all_fields(): void
{
Livewire::actingAs($this->user)
->test(PhaseList::class, ['project' => $this->project])
->call('openForm')
->set('name', 'Cimentación')
->set('description', 'Zapatas y muros')
->set('color', '#ff0000')
->set('order', 2)
->set('progressPercent', 30)
->set('plannedStart', '2026-07-01')
->set('plannedEnd', '2026-08-01')
->call('save')
->assertHasNoErrors();
$this->assertDatabaseHas('phases', [
'project_id' => $this->project->id,
'name' => 'Cimentación',
'color' => '#ff0000',
'order' => 2,
'progress_percent' => 30,
]);
}
public function test_edit_phase_loads_and_updates(): void
{
$phase = $this->project->phases()->create([
'name' => 'Vieja', 'order' => 1, 'color' => '#3b82f6', 'progress_percent' => 0,
]);
Livewire::actingAs($this->user)
->test(PhaseList::class, ['project' => $this->project])
->call('editPhase', $phase->id)
->assertSet('name', 'Vieja')
->set('name', 'Nueva')
->set('progressPercent', 80)
->call('save')
->assertHasNoErrors();
$phase->refresh();
$this->assertEquals('Nueva', $phase->name);
$this->assertEquals(80, $phase->progress_percent);
}
public function test_phase_table_lists_and_deletes(): void
{
$phase = $this->project->phases()->create([
'name' => 'Estructura', 'order' => 1, 'color' => '#3b82f6', 'progress_percent' => 0,
]);
Livewire::actingAs($this->user)
->test(PhaseTable::class, ['projectId' => $this->project->id])
->assertOk()
->assertSee('Estructura')
->call('deletePhase', $phase->id);
$this->assertDatabaseMissing('phases', ['id' => $phase->id, 'deleted_at' => null]);
}
public function test_create_phase_requires_manage_phases_permission(): void
{
$outsider = User::factory()->create();
$this->project->users()->attach($outsider->id, ['role_in_project' => 'viewer']);
Livewire::actingAs($outsider)
->test(PhaseList::class, ['project' => $this->project])
->call('openForm')
->assertForbidden();
}
}