diff --git a/android-app/app/build.gradle.kts b/android-app/app/build.gradle.kts index 93e0d54..f8093e2 100644 --- a/android-app/app/build.gradle.kts +++ b/android-app/app/build.gradle.kts @@ -12,8 +12,8 @@ android { applicationId = "com.korexlabs.dhinspeccion" minSdk = 26 targetSdk = 36 - versionCode = 33 - versionName = "0.19.5" + versionCode = 34 + versionName = "0.19.6" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/ModernVisitRoot.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/ModernVisitRoot.kt index d6cf377..e1420d6 100644 --- a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/ModernVisitRoot.kt +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/ModernVisitRoot.kt @@ -63,6 +63,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment +import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager @@ -778,6 +779,11 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit) enabled = !model.busy, modifier = Modifier.fillMaxWidth().menuAnchor(MenuAnchorType.PrimaryEditable), singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + keyboardActions = KeyboardActions(onNext = { + familyExpanded = false + focusManager.moveFocus(FocusDirection.Down) + }), ) ExposedDropdownMenu( expanded = familyExpanded && !model.busy, @@ -818,9 +824,11 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit) value = name, onValueChange = { name = it }, label = { Text("Nombre técnico *") }, - supportingText = { Text("Usá la denominación técnica del elemento. El código DH se genera automáticamente.") }, + placeholder = { Text("Denominación técnica") }, modifier = Modifier.fillMaxWidth(), singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Down) }), ) OutlinedTextField( value = commonName, @@ -828,17 +836,24 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit) label = { Text("Nombre habitual (opcional)") }, modifier = Modifier.fillMaxWidth(), singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Down) }), ) OutlinedTextField( value = description, onValueChange = { description = it }, label = { Text("Descripción (opcional)") }, - supportingText = { Text("Dato equivalente al alta desde el panel web.") }, modifier = Modifier.fillMaxWidth(), - minLines = 2, + singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = if (editableAttributes.isEmpty()) ImeAction.Done else ImeAction.Next), + keyboardActions = KeyboardActions( + onNext = { focusManager.moveFocus(FocusDirection.Down) }, + onDone = { keyboard?.hide(); focusManager.clearFocus() }, + ), ) - editableAttributes.forEach { definition -> + editableAttributes.forEachIndexed { index, definition -> + val isLastAttribute = index == editableAttributes.lastIndex OutlinedTextField( value = attributeValues[definition.code].orEmpty(), onValueChange = { attributeValues[definition.code] = it }, @@ -849,8 +864,14 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit) }, keyboardOptions = KeyboardOptions( keyboardType = if (definition.dataType.uppercase() in setOf("NUMBER", "DECIMAL", "INTEGER", "FLOAT")) KeyboardType.Decimal else KeyboardType.Text, + imeAction = if (isLastAttribute) ImeAction.Done else ImeAction.Next, + ), + keyboardActions = KeyboardActions( + onNext = { focusManager.moveFocus(FocusDirection.Down) }, + onDone = { keyboard?.hide(); focusManager.clearFocus() }, ), modifier = Modifier.fillMaxWidth(), + singleLine = true, ) } diff --git a/android-app/app/src/main/res/drawable/ic_mendoza_launcher_exact.webp b/android-app/app/src/main/res/drawable/ic_mendoza_launcher_exact.webp index 7dfd9be..f59e5c3 100644 Binary files a/android-app/app/src/main/res/drawable/ic_mendoza_launcher_exact.webp and b/android-app/app/src/main/res/drawable/ic_mendoza_launcher_exact.webp differ diff --git a/android-app/app/src/test/java/com/korexlabs/dhinspeccion/ReleaseMetadataTest.kt b/android-app/app/src/test/java/com/korexlabs/dhinspeccion/ReleaseMetadataTest.kt index 7bc4636..fe1b198 100644 --- a/android-app/app/src/test/java/com/korexlabs/dhinspeccion/ReleaseMetadataTest.kt +++ b/android-app/app/src/test/java/com/korexlabs/dhinspeccion/ReleaseMetadataTest.kt @@ -8,8 +8,8 @@ class ReleaseMetadataTest { @Test fun debugBuildKeepsSeparateApplicationIdentity() { assertEquals("com.korexlabs.dhinspeccion.debug", BuildConfig.APPLICATION_ID) - assertEquals(33, BuildConfig.VERSION_CODE) - assertEquals("0.19.5-debug", BuildConfig.VERSION_NAME) + assertEquals(34, BuildConfig.VERSION_CODE) + assertEquals("0.19.6-debug", BuildConfig.VERSION_NAME) } @Test diff --git a/api-v3/src/asset-master/assets.service.ts b/api-v3/src/asset-master/assets.service.ts index f2fb7a4..35ffb14 100644 --- a/api-v3/src/asset-master/assets.service.ts +++ b/api-v3/src/asset-master/assets.service.ts @@ -760,6 +760,7 @@ export class AssetsService { parentId: dto.parentId, operationalAreaId: dto.operationalAreaId, operatorCompanyId: dto.operatorCompanyId, + inventoryFamilyId: dto.inventoryFamilyId ?? null, code: dto.code, name: dto.name, commonName: dto.commonName ?? null, diff --git a/api-v3/src/asset-master/dto/create-field-discovery.dto.ts b/api-v3/src/asset-master/dto/create-field-discovery.dto.ts index afe76d0..d063afa 100644 --- a/api-v3/src/asset-master/dto/create-field-discovery.dto.ts +++ b/api-v3/src/asset-master/dto/create-field-discovery.dto.ts @@ -36,6 +36,10 @@ export class CreateFieldDiscoveryDto { @IsUUID('4') operatorCompanyId!: string; + @IsOptional() + @IsUUID('4') + inventoryFamilyId?: string | null; + @IsOptional() @Transform(({ value }) => typeof value === 'string' && value.trim() ? value.trim() : null) @IsString() diff --git a/api-v3/src/inspection-visits/field-inventory.service.ts b/api-v3/src/inspection-visits/field-inventory.service.ts index eb60a8b..4e9c8cf 100644 --- a/api-v3/src/inspection-visits/field-inventory.service.ts +++ b/api-v3/src/inspection-visits/field-inventory.service.ts @@ -295,6 +295,7 @@ export class FieldInventoryService { operationalAreaId: context.areaId, // Compatibility-only creation snapshot. Membership never depends on it. operatorCompanyId: context.companyId, + inventoryFamilyId: dto.familyId ?? null, description: dto.description ?? null, discoveryNotes: dto.discoveryNotes ?? null, attributes: dto.attributes, diff --git a/api-v3/test/unit/f5-android-test-cut.test.ts b/api-v3/test/unit/f5-android-test-cut.test.ts index 295b560..65512e7 100644 --- a/api-v3/test/unit/f5-android-test-cut.test.ts +++ b/api-v3/test/unit/f5-android-test-cut.test.ts @@ -10,8 +10,8 @@ function mountedRepoFile(path: string): string { test('F6.3 Android test cut targets production API and has a distinct installable debug version', () => { const gradle = mountedRepoFile('android-app/app/build.gradle.kts'); - assert.match(gradle, /versionCode = 33/); - assert.match(gradle, /versionName = "0\.19\.5"/); + assert.match(gradle, /versionCode = 34/); + assert.match(gradle, /versionName = "0\.19\.6"/); assert.match(gradle, /https:\/\/dhv2\.korexlabs\.com\/api\/v3\//); assert.match(gradle, /applicationIdSuffix = "\.debug"/); }); @@ -26,3 +26,18 @@ test('F5/F6.3 field inventory exposes Other families as reviewable choices to An assert.match(service, /AS "isOther"/); assert.match(service, /isOtherFamily: family\.isOther/); }); + + +test('field inventory persists the validated Tipo de instalación in the initial asset insert', () => { + const fieldService = readFileSync( + resolve(process.cwd(), 'src/inspection-visits/field-inventory.service.ts'), + 'utf8', + ); + const assetsService = readFileSync( + resolve(process.cwd(), 'src/asset-master/assets.service.ts'), + 'utf8', + ); + + assert.match(fieldService, /inventoryFamilyId: dto\.familyId \?\? null/); + assert.match(assetsService, /inventoryFamilyId: dto\.inventoryFamilyId \?\? null/); +}); diff --git a/web-v2/index.html b/web-v2/index.html index 10e2225..80d4c8c 100644 --- a/web-v2/index.html +++ b/web-v2/index.html @@ -4,6 +4,7 @@ + DH Inspección V2 diff --git a/web-v2/public/favicon.webp b/web-v2/public/favicon.webp new file mode 100644 index 0000000..f59e5c3 Binary files /dev/null and b/web-v2/public/favicon.webp differ