diff --git a/android-app/RELEASE.md b/android-app/RELEASE.md index 46a445b..436d784 100644 --- a/android-app/RELEASE.md +++ b/android-app/RELEASE.md @@ -1,20 +1,26 @@ -# DH Inspección Android · release final de campo 0.19.7 +# DH Inspección Android · release final de campo 0.19.8 ## Candidata vigente - Fase funcional: **Flujo final de campo · Inspección → Acta → Hallazgos → Firma**. -- `versionName`: **0.19.7**. +- `versionName`: **0.19.8**. - `versionCode`: **35**. - Application ID release: `com.korexlabs.dhinspeccion`. - Application ID debug/QA: `com.korexlabs.dhinspeccion.debug`. - API: `https://dhv2.korexlabs.com/api/v3/`. -- Servidor compatible de esta candidata: **API 0.29.0-5 / WEB 0.23.0-3**. +- Servidor compatible de esta candidata: **API 0.29.0-6 / WEB 0.23.0-3**. La variante debug es independiente de la app productiva y puede instalarse para QA/presentación sin sobrescribir una instalación release histórica. +### Ajustes 0.19.8 + +- El encabezado de **Mis inspecciones** separa las acciones para que **Salir** nunca se comprima ni se corte en pantallas angostas. +- El alta de Hallazgos corrige la captura de versión que podía transformar el número en `NaN` después del INSERT y devolver error 500. +- Un Hallazgo admite **múltiples fotografías con GPS**; después de cada captura queda disponible **Agregar otra foto con GPS** y se muestra el contador. + ## Procedimiento operativo validado -La APK 0.19.7 fija como recorrido principal de campo: +La APK 0.19.8 fija como recorrido principal de campo: 1. **Iniciar Inspección**. Al iniciarla se habilita el circuito de **Actas y Hallazgos**; Inventario no es una acción independiente de la Inspección. 2. **Crear o abrir un Acta**. Puede haber varias Actas dentro de una misma Inspección, pero sólo una puede permanecer en elaboración al mismo tiempo. La urgencia todavía no se define. diff --git a/android-app/app/build.gradle.kts b/android-app/app/build.gradle.kts index b941cfd..105a587 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 = 35 - versionName = "0.19.7" + versionCode = 36 + versionName = "0.19.8" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/FieldFindingScreen.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/FieldFindingScreen.kt index a2dff43..a77fedf 100644 --- a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/FieldFindingScreen.kt +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/FieldFindingScreen.kt @@ -238,13 +238,18 @@ fun FieldFindingScreen(model: MainViewModel) { style = MaterialTheme.typography.bodySmall, ) } + val photoCount = evidence.count { it.kind == "PHOTO" } Button( onClick = { requestPhoto(finding.id) }, enabled = !model.busy, modifier = Modifier.fillMaxWidth(), ) { - Text("Tomar foto con GPS") + Text(if (photoCount == 0) "Agregar foto con GPS" else "Agregar otra foto con GPS") } + Text( + if (photoCount == 0) "Podés adjuntar una o varias fotos." else "$photoCount foto${if (photoCount == 1) "" else "s"} adjunta${if (photoCount == 1) "" else "s"}. Podés seguir agregando.", + style = MaterialTheme.typography.bodySmall, + ) } } } @@ -365,7 +370,8 @@ fun FieldFindingScreen(model: MainViewModel) { label = { Text("Qué observaste *") }, placeholder = { Text("Describí brevemente el problema") }, modifier = Modifier.fillMaxWidth(), - singleLine = true, + minLines = 3, + maxLines = 6, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Down) }), ) @@ -405,13 +411,19 @@ fun FieldFindingScreen(model: MainViewModel) { Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { Text("Hallazgo registrado", fontWeight = FontWeight.Bold) Text("${finding.code} · ${finding.title}") + val createdEvidence = model.fieldFindingEvidence[finding.id].orEmpty() + val createdPhotoCount = createdEvidence.count { it.kind == "PHOTO" } Button( onClick = { requestPhoto(finding.id) }, enabled = !model.busy, modifier = Modifier.fillMaxWidth(), ) { - Text("Tomar foto con GPS") + Text(if (createdPhotoCount == 0) "Agregar foto con GPS" else "Agregar otra foto con GPS") } + Text( + if (createdPhotoCount == 0) "Podés adjuntar todas las fotos que necesites." else "Fotos adjuntas: $createdPhotoCount. Podés seguir agregando.", + style = MaterialTheme.typography.bodySmall, + ) Text("También podés registrar otro Hallazgo sobre el mismo Inventario.", style = MaterialTheme.typography.bodySmall) } } diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/MobileHomeScreen.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/MobileHomeScreen.kt index 8f6d5f7..12fa386 100644 --- a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/MobileHomeScreen.kt +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/MobileHomeScreen.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items @@ -86,21 +85,33 @@ fun MobileHomeScreen(model: MainViewModel) { } Column(Modifier.fillMaxSize().padding(top = 28.dp)) { - Row( + Column( Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 10.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, + verticalArrangement = Arrangement.spacedBy(8.dp), ) { Column { Text("Mis inspecciones", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold) Text(session.displayName, style = MaterialTheme.typography.bodySmall) } - Row { - OutlinedButton(onClick = { model.loadVisits() }, enabled = !model.busy && !opening) { - Text("Actualizar") + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + OutlinedButton( + onClick = { model.loadVisits() }, + enabled = !model.busy && !opening, + modifier = Modifier.weight(1f), + ) { + Text("Actualizar", maxLines = 1) + } + OutlinedButton( + onClick = { model.logout() }, + enabled = !opening, + modifier = Modifier.weight(1f), + ) { + Text("Salir", maxLines = 1) } - Spacer(Modifier.width(8.dp)) - OutlinedButton(onClick = { model.logout() }, enabled = !opening) { Text("Salir") } } } 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 26727de..a89f30d 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(35, BuildConfig.VERSION_CODE) - assertEquals("0.19.7-debug", BuildConfig.VERSION_NAME) + assertEquals(36, BuildConfig.VERSION_CODE) + assertEquals("0.19.8-debug", BuildConfig.VERSION_NAME) } @Test diff --git a/api-v3/package-lock.json b/api-v3/package-lock.json index 4569f16..5298ae9 100644 --- a/api-v3/package-lock.json +++ b/api-v3/package-lock.json @@ -1,12 +1,12 @@ { "name": "dhv2-api", - "version": "0.29.0-5", + "version": "0.29.0-6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dhv2-api", - "version": "0.29.0-5", + "version": "0.29.0-6", "license": "UNLICENSED", "dependencies": { "@nestjs/common": "^11.0.0", diff --git a/api-v3/package.json b/api-v3/package.json index 4866a63..466322c 100644 --- a/api-v3/package.json +++ b/api-v3/package.json @@ -1,6 +1,6 @@ { "name": "dhv2-api", - "version": "0.29.0-5", + "version": "0.29.0-6", "private": true, "license": "UNLICENSED", "scripts": { diff --git a/api-v3/src/inspection-findings/inspection-findings.service.ts b/api-v3/src/inspection-findings/inspection-findings.service.ts index 54bc171..53e6693 100644 --- a/api-v3/src/inspection-findings/inspection-findings.service.ts +++ b/api-v3/src/inspection-findings/inspection-findings.service.ts @@ -1166,10 +1166,13 @@ export class InspectionFindingsService { principal: AuthPrincipal, ): Promise { const [row] = await manager.query(` - UPDATE inspection_findings - SET current_version = current_version + 1 - WHERE id = $1 - RETURNING current_version AS "versionNumber" + WITH updated AS ( + UPDATE inspection_findings + SET current_version = current_version + 1 + WHERE id = $1 + RETURNING current_version + ) + SELECT current_version AS "versionNumber" FROM updated `, [finding.id]) as Array<{ versionNumber: number }>; const versionNumber = Number(row.versionNumber); finding.currentVersion = versionNumber; diff --git a/api-v3/src/version.ts b/api-v3/src/version.ts index 6664a02..f71daac 100644 --- a/api-v3/src/version.ts +++ b/api-v3/src/version.ts @@ -1,2 +1,2 @@ -export const API_VERSION = '0.29.0-5'; +export const API_VERSION = '0.29.0-6'; export const API_PHASE = 'F6.2'; diff --git a/api-v3/test/unit/f4-health-metadata.test.ts b/api-v3/test/unit/f4-health-metadata.test.ts index dfbdd03..bb859fc 100644 --- a/api-v3/test/unit/f4-health-metadata.test.ts +++ b/api-v3/test/unit/f4-health-metadata.test.ts @@ -8,5 +8,5 @@ test('health metadata reports the current F6.1 release', () => { assert.equal(API_PHASE, 'F6.2'); const pkg = JSON.parse(readFileSync(resolve(process.cwd(), 'package.json'), 'utf8')) as { version: string }; assert.equal(API_VERSION, pkg.version); - assert.equal(API_VERSION, '0.29.0-5'); + assert.equal(API_VERSION, '0.29.0-6'); }); \ No newline at end of file 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 cad00c4..f0c089f 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 = 35/); - assert.match(gradle, /versionName = "0\.19\.7"/); + assert.match(gradle, /versionCode = 36/); + assert.match(gradle, /versionName = "0\.19\.8"/); assert.match(gradle, /https:\/\/dhv2\.korexlabs\.com\/api\/v3\//); assert.match(gradle, /applicationIdSuffix = "\.debug"/); }); diff --git a/api-v3/test/unit/f6-6-mobile-finding-resilience.test.ts b/api-v3/test/unit/f6-6-mobile-finding-resilience.test.ts new file mode 100644 index 0000000..8c85f61 --- /dev/null +++ b/api-v3/test/unit/f6-6-mobile-finding-resilience.test.ts @@ -0,0 +1,37 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import test from 'node:test'; + +function source(path: string): string { + return readFileSync(resolve(process.cwd(), path), 'utf8'); +} + +test('F6.6 Hallazgo version capture uses a SELECT-shaped CTE so TypeORM never turns the version into NaN', () => { + const service = source('src/inspection-findings/inspection-findings.service.ts'); + + assert.match(service, /WITH updated AS \(/); + assert.match(service, /UPDATE inspection_findings/); + assert.match(service, /RETURNING current_version/); + assert.match(service, /SELECT current_version AS "versionNumber" FROM updated/); + assert.match(service, /const versionNumber = Number\(row\.versionNumber\)/); +}); + +test('F6.6 mobile home keeps Salir readable on narrow screens', () => { + const home = source('../android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/MobileHomeScreen.kt'); + + assert.match(home, /Text\("Actualizar", maxLines = 1\)/); + assert.match(home, /Text\("Salir", maxLines = 1\)/); + assert.match(home, /modifier = Modifier\.weight\(1f\)/); +}); + +test('F6.6 Hallazgos can receive as many field photos as needed', () => { + const screen = source('../android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/FieldFindingScreen.kt'); + const model = source('../android-app/app/src/main/java/com/korexlabs/dhinspeccion/MainViewModel.kt'); + + assert.match(screen, /Agregar foto con GPS/); + assert.match(screen, /Agregar otra foto con GPS/); + assert.match(screen, /Podés adjuntar todas las fotos que necesites/); + assert.match(model, /loadEvidenceInternal\(findingId\)/); + assert.doesNotMatch(screen, /máximo de 1 foto|solo una foto/i); +}); \ No newline at end of file