/** * Formulario de inspección generado dinámicamente desde los `fields` de una * plantilla. El renderer es tolerante: deduce key/label/type de varias formas. * * API v1.1: cada campo puede traer `group` (sección de agrupación), * `question` (prompt corto mostrado como etiqueta principal), `help` * (texto de ayuda bajo el campo) y `required`. */ import { NativeStackScreenProps } from '@react-navigation/native-stack'; import React, { useCallback, useEffect, useState } from 'react'; import { Alert, ScrollView, StyleSheet, Switch, Text, TouchableOpacity, View } from 'react-native'; import { Template } from '../api/types'; import { getTemplates } from '../db/repositories'; import { createInspection } from '../sync/mutations'; import { getDb } from '../db/database'; import { newUuid, nextTempId } from '../sync/uuid'; import { RootStackParamList } from '../navigation/types'; import { MediaStrip } from '../ui/MediaStrip'; import { Card, ChipSelect, COLORS, Field, PrimaryButton, SectionTitle, } from '../ui/components'; type Props = NativeStackScreenProps; interface NormField { key: string; label: string; type: 'text' | 'textarea' | 'number' | 'boolean' | 'select'; options: string[]; group: string; help: string | null; required: boolean; } function normalizeField(raw: unknown, idx: number): NormField { const f = (raw ?? {}) as Record; const key = String(f.key ?? f.name ?? f.id ?? `field_${idx}`); // `question` (v1.1) tiene prioridad como etiqueta visible. const label = String(f.question ?? f.label ?? f.name ?? f.key ?? key); let type = String(f.type ?? 'text').toLowerCase(); if (!['text', 'textarea', 'number', 'boolean', 'select'].includes(type)) { if (type === 'checkbox' || type === 'bool') type = 'boolean'; else if (type === 'dropdown') type = 'select'; else type = 'text'; } const options = Array.isArray(f.options) ? (f.options as unknown[]).map((o) => typeof o === 'string' ? o : String((o as Record)?.value ?? o), ) : []; return { key, label, type: type as NormField['type'], options, group: typeof f.group === 'string' ? f.group : '', help: typeof f.help === 'string' && f.help ? f.help : null, required: f.required === true, }; } /** Agrupa los campos por `group` preservando el orden de aparición. */ function groupFields(fields: NormField[]): { group: string; items: NormField[] }[] { const out: { group: string; items: NormField[] }[] = []; for (const f of fields) { const last = out[out.length - 1]; if (last && last.group === f.group) last.items.push(f); else out.push({ group: f.group, items: [f] }); } return out; } const RESULTS = ['pass', 'fail', 'na'] as const; export function InspectionFormScreen({ route, navigation }: Props) { const { featureId, featureName, templateId: suggestedId } = route.params; // Paso 1: elegir plantilla (las asignadas al proyecto llegan en el bundle). // Paso 2: rellenar el formulario. `chosen` = false → aún en el selector. const [available, setAvailable] = useState([]); const [chosen, setChosen] = useState(false); const [template, setTemplate] = useState