diff --git a/app/Livewire/Inspections/GlobalTemplateManager.php b/app/Livewire/Inspections/GlobalTemplateManager.php index a22bc1d..f6db27e 100644 --- a/app/Livewire/Inspections/GlobalTemplateManager.php +++ b/app/Livewire/Inspections/GlobalTemplateManager.php @@ -163,9 +163,11 @@ class GlobalTemplateManager extends Component public function downloadExampleCsv() { $headers = ['Content-Type' => 'text/csv']; - $csv = "name,label,type,required,options,min,max,step\n" - . "altura,Altura (m),decimal,1,,0,100,0.1\n" - . "ok,¿OK?,boolean,1,,,,\n"; + $csv = "\xEF\xBB\xBF" // BOM UTF-8 (para que Excel respete los acentos) + . "group,name,label,question,type,required,options,min,max,step,help\n" + . "Dimensiones,altura,Altura (m),¿Cumple la altura de proyecto?,decimal,1,,0,100,0.1,Medir con flexómetro\n" + . "Dimensiones,material,Material,,select,1,Hormigón|Acero|Madera,,,,\n" + . "Acabados,ok,¿Acabado correcto?,,boolean,1,,,,,\n"; return response()->streamDownload(fn () => print($csv), 'plantilla_ejemplo.csv', $headers); } @@ -217,12 +219,17 @@ class GlobalTemplateManager extends Component $ext = strtolower($this->importFile->getClientOriginalExtension()); $path = $this->importFile->getRealPath(); + // Fila no vacía = tiene al menos una celda con contenido (no filtramos por + // la primera columna, que ahora es `group` y puede ir vacía). parseRows() + // ya descarta las filas sin `name`. + $notEmpty = fn ($r) => count(array_filter((array) $r, fn ($c) => trim((string) $c) !== '')) > 0; + if ($ext === 'xlsx' || $ext === 'xls') { $spreadsheet = IOFactory::load($path); $sheet = $spreadsheet->getActiveSheet(); $rows = $sheet->toArray(null, true, true, false); array_shift($rows); - return array_filter($rows, fn ($r) => !empty($r[0])); + return array_values(array_filter($rows, $notEmpty)); } $rows = []; @@ -231,7 +238,7 @@ class GlobalTemplateManager extends Component if ($bom !== "\xEF\xBB\xBF") rewind($handle); fgetcsv($handle); while (($row = fgetcsv($handle)) !== false) { - if (!empty($row[0])) $rows[] = $row; + if ($notEmpty($row)) $rows[] = $row; } fclose($handle); return $rows; @@ -239,21 +246,26 @@ class GlobalTemplateManager extends Component private function parseRows(array $rows): array { + // Orden de columnas: + // group, name, label, question, type, required, options, min, max, step, help $fields = []; foreach ($rows as $row) { $row = array_values((array) $row); - $rawName = trim($row[0] ?? ''); + $rawName = trim($row[1] ?? ''); if ($rawName === '') continue; $fields[] = [ + 'group' => trim($row[0] ?? ''), 'name' => $this->slugify($rawName), - 'label' => trim($row[1] ?? $rawName), - 'type' => $this->normalizeType($row[2] ?? 'text'), - 'required' => in_array(strtolower(trim($row[3] ?? '0')), ['1', 'si', 'sí', 'yes', 'true']), - 'options' => trim($row[4] ?? ''), - 'min' => ($row[5] ?? '') !== '' ? $row[5] : null, - 'max' => ($row[6] ?? '') !== '' ? $row[6] : null, - 'step' => ($row[7] ?? '') !== '' ? $row[7] : null, + 'label' => trim($row[2] ?? '') ?: $rawName, + 'question' => trim($row[3] ?? ''), + 'type' => $this->normalizeType($row[4] ?? 'text'), + 'required' => in_array(strtolower(trim($row[5] ?? '0')), ['1', 'si', 'sí', 'yes', 'true']), + 'options' => trim($row[6] ?? ''), + 'min' => ($row[7] ?? '') !== '' ? $row[7] : null, + 'max' => ($row[8] ?? '') !== '' ? $row[8] : null, + 'step' => ($row[9] ?? '') !== '' ? $row[9] : null, + 'help' => trim($row[10] ?? ''), ]; } return $fields; diff --git a/resources/views/livewire/inspections/global-template-manager.blade.php b/resources/views/livewire/inspections/global-template-manager.blade.php index fe66561..5b4a815 100644 --- a/resources/views/livewire/inspections/global-template-manager.blade.php +++ b/resources/views/livewire/inspections/global-template-manager.blade.php @@ -122,7 +122,7 @@

- {{ __('Columns: name, label, type, required, options, min, max, step') }}. + {{ __('Columns') }}: group, name, label, question, type, required, options, min, max, step, help.

@@ -144,10 +144,17 @@

{{ count($importPreviewFields) }} {{ __('fields detected') }}:

- + @foreach($importPreviewFields as $f) - + + + + + + + + @endforeach
{{ __('Label') }}{{ __('Name') }}{{ __('Type') }}{{ __('Required') }}
{{ __('Group') }}{{ __('Label') }}{{ __('Question') }}{{ __('Name') }}{{ __('Type') }}{{ __('Required') }}
{{ $f['label'] }}{{ $f['name'] }}{{ $f['type'] }}{{ !empty($f['required']) ? '✓' : '' }}
{{ $f['group'] ?? '' }}{{ $f['label'] }}{{ $f['question'] ?? '' }}{{ $f['name'] }}{{ $f['type'] }}{{ !empty($f['required']) ? '✓' : '' }}
diff --git a/tests/Feature/GlobalTemplatesTest.php b/tests/Feature/GlobalTemplatesTest.php index 4a39911..280e8f7 100644 --- a/tests/Feature/GlobalTemplatesTest.php +++ b/tests/Feature/GlobalTemplatesTest.php @@ -12,6 +12,7 @@ use App\Models\Phase; use App\Models\Project; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Http\UploadedFile; use Livewire\Livewire; use Spatie\Permission\Models\Permission; use Tests\TestCase; @@ -116,6 +117,43 @@ class GlobalTemplatesTest extends TestCase ->assertSet('editingTemplate', $tpl->id); } + public function test_csv_import_parses_group_question_and_help(): void + { + Permission::findOrCreate('manage templates'); + $admin = User::factory()->create(); + $admin->givePermissionTo('manage templates'); + + $csv = "group,name,label,question,type,required,options,min,max,step,help\n" + . "Dimensiones,altura,Altura (m),¿Cumple la cota?,decimal,1,,0,100,0.1,Medir con flexómetro\n" + . "Acabados,ok,¿Acabado?,,boolean,0,,,,,\n"; + $file = UploadedFile::fake()->createWithContent('plantilla.csv', $csv); + + $cmp = Livewire::actingAs($admin) + ->test(GlobalTemplateManager::class) + ->set('importTemplateName', 'Importada') + ->set('importFile', $file) + ->call('parseImportFile') + ->assertHasNoErrors(); + + $fields = $cmp->get('importPreviewFields'); + $this->assertCount(2, $fields); + $this->assertEquals('Dimensiones', $fields[0]['group']); + $this->assertEquals('altura', $fields[0]['name']); + $this->assertEquals('¿Cumple la cota?', $fields[0]['question']); + $this->assertEquals('Medir con flexómetro', $fields[0]['help']); + $this->assertEquals('decimal', $fields[0]['type']); + $this->assertTrue($fields[0]['required']); + + // Fila sin group (columna 1 vacía) NO debe descartarse + $this->assertEquals('Acabados', $fields[1]['group']); + $this->assertEquals('ok', $fields[1]['name']); + + $cmp->call('confirmImportFile'); + $tpl = InspectionTemplate::where('name', 'Importada')->first(); + $this->assertNotNull($tpl); + $this->assertEquals('¿Cumple la cota?', $tpl->fields[0]['question']); + } + public function test_map_template_selector_only_shows_assigned_templates(): void { $user = User::factory()->create();