feat(templates): importar CSV/Excel con group, question y help

El importador de plantillas solo parseaba name/label/type/required/options/
min/max/step, dejando fuera group (sección), question (pregunta corta) y help
(ayuda) que el builder manual sí soporta.

- Nuevo orden de columnas: group,name,label,question,type,required,options,min,max,step,help
- parseRows() rellena las 11 claves (coincide con addField()).
- readFileRows() ya no descarta filas por la 1ª columna (ahora es `group`, que
  puede ir vacía); filtra por "fila con algún contenido" y parseRows descarta
  las que no tengan `name`.
- CSV de ejemplo actualizado (con BOM UTF-8) y con filas que muestran group/
  question/help/select.
- Modal de importación: texto de columnas y tabla de preview muestran Grupo y
  Pregunta.

Tests: nuevo test csv_import_parses_group_question_and_help. Suite 95 passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 12:43:54 +02:00
co-authored by Claude Opus 4.8
parent f3367ee1ea
commit de951cc8e4
3 changed files with 73 additions and 16 deletions
@@ -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;