diff --git a/android-app/RELEASE.md b/android-app/RELEASE.md index b649dff..dc9dca6 100644 --- a/android-app/RELEASE.md +++ b/android-app/RELEASE.md @@ -1,7 +1,17 @@ -# DH Inspección Android · release final de campo 0.19.10 +# DH Inspección Android · release de campo 0.19.11 ## Candidata vigente +- `versionName`: **0.19.11**; `versionCode`: **39**. +- API: `https://dhv2.korexlabs.com/api/v3/`; compatible con API 0.29.0-9 / WEB 0.23.0-6. +- Antes de cerrar el contenido, el inspector escribe una descripción real de lo actuado. +- La descripción se sincroniza antes del bloqueo, también cuando se trabajó sin conexión. +- Un acta anterior, ya sellada, conserva el texto originalmente registrado. + +## Referencia de 0.19.10 + +### Candidata anterior + - Fase funcional: **Flujo final de campo · Inspección → Acta → Hallazgos → Firma**. - `versionName`: **0.19.10**. - `versionCode`: **38**. diff --git a/android-app/app/build.gradle.kts b/android-app/app/build.gradle.kts index 0bf2b85..2741171 100644 --- a/android-app/app/build.gradle.kts +++ b/android-app/app/build.gradle.kts @@ -8,13 +8,14 @@ plugins { android { namespace = "com.korexlabs.dhinspeccion" compileSdk = 36 + buildToolsVersion = "36.0.0" defaultConfig { applicationId = "com.korexlabs.dhinspeccion" minSdk = 26 targetSdk = 36 - versionCode = 38 - versionName = "0.19.10" + versionCode = 39 + versionName = "0.19.11" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/MainViewModel.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/MainViewModel.kt index d47dacd..279adf9 100644 --- a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/MainViewModel.kt +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/MainViewModel.kt @@ -260,7 +260,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { status = "DRAFT", occurredAt = occurredAt, title = "Acta de inspección ${currentVisit.code}", - summary = "Acta de inspección en curso. Los Hallazgos y observaciones se incorporan de forma trazable durante la inspección.", + summary = "Se inició la inspección ${currentVisit.code}.", ) selectedAct = local acts = acts + MobileActSummary( @@ -733,16 +733,22 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { } } - fun prepareSelectedAct(urgency: String) { + fun prepareSelectedAct(urgency: String, narrative: String) { val actId = selectedAct?.id ?: return if (urgency !in setOf("URGENT", "NON_URGENT")) { error = "Definí la urgencia antes de cerrar el Acta." return } + val summary = narrative.trim() + if (summary.length !in 10..20000) { + error = "Describí lo actuado antes de cerrar el Acta (entre 10 y 20000 caracteres)." + return + } launchBusy(mutation = true) { + offlineQueue.enqueueActSummary(actId, summary) offlineQueue.enqueueActLock(actId, urgency) - selectedAct = selectedAct?.copy(status = "LOCKED", urgency = urgency) - acts = acts.map { if (it.id == actId) it.copy(status = "LOCKED", urgency = urgency) else it } + selectedAct = selectedAct?.copy(status = "LOCKED", urgency = urgency, summary = summary) + acts = acts.map { if (it.id == actId) it.copy(status = "LOCKED", urgency = urgency, summary = summary) else it } actClosure = actClosure?.copy(act = actClosure!!.act.copy(status = "LOCKED", urgency = urgency)) val result = offlineQueue.syncAll() refreshOfflineStateInternal() diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/MobileActs.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/MobileActs.kt index c2a63b9..efaa315 100644 --- a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/MobileActs.kt +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/MobileActs.kt @@ -106,7 +106,8 @@ data class PrepareMobileActRequest( ) data class UpdateMobileActRequest( - val assetIds: List, + val assetIds: List? = null, + val summary: String? = null, ) data class MobileResponsibleRequest( @@ -367,7 +368,7 @@ class MobileActsRepository(context: Context) { clientGeneratedId = clientGeneratedId, occurredAt = occurredAt, title = "Acta de inspección $visitCode", - summary = "Acta de inspección en curso. Los Hallazgos y observaciones se incorporan de forma trazable durante la inspección.", + summary = "Se inició la inspección $visitCode.", assetIds = emptyList(), ), ) @@ -376,6 +377,10 @@ class MobileActsRepository(context: Context) { return created } + suspend fun updateSummary(actId: String, summary: String): MobileActDetail = authorized { session -> + api.updateAct("Bearer ${session.accessToken}", actId, UpdateMobileActRequest(summary = summary)) + } + suspend fun ensureAsset(actId: String, assetId: String): MobileActDetail { val detail = get(actId) if (detail.assets.any { it.id == assetId }) return detail diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/offline/OfflineQueue.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/offline/OfflineQueue.kt index 37dfbf4..0c98631 100644 --- a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/offline/OfflineQueue.kt +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/offline/OfflineQueue.kt @@ -256,6 +256,12 @@ class OfflineMutationQueue(private val context: Context) { .toString(), ) + suspend fun enqueueActSummary(actId: String, summary: String): String = enqueueJson( + type = "ACT_SUMMARY", + resourceId = actId, + payload = JSONObject().put("summary", summary).toString(), + ) + suspend fun enqueueActAsset(actId: String, assetId: String): String = enqueueJson( type = "ACT_ASSET_ENSURE", resourceId = actId, @@ -450,6 +456,10 @@ class OfflineMutationQueue(private val context: Context) { clientGeneratedId = operation.resourceId, occurredAt = payload.getString("occurredAt"), ) + "ACT_SUMMARY" -> MobileActsRepository(context).updateSummary( + operation.resourceId, + payload.getString("summary"), + ) "ACT_ASSET_ENSURE" -> MobileActsRepository(context).ensureAsset( operation.resourceId, payload.getString("assetId"), diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/MobileActsScreen.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/MobileActsScreen.kt index 40e6738..369727d 100644 --- a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/MobileActsScreen.kt +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/MobileActsScreen.kt @@ -62,6 +62,9 @@ fun MobileActsScreen( val scope = rememberCoroutineScope() var closingUrgency by rememberSaveable(selected?.id) { mutableStateOf("") } + var actNarrative by rememberSaveable(selected?.id) { + mutableStateOf(selected?.summary.takeUnless { it == null || it.startsWith("Se inició la inspección ") || it.startsWith("Acta de inspección en curso.") }.orEmpty()) + } var attendance by rememberSaveable(selected?.id) { mutableStateOf(representativeSeed?.attendanceStatus ?: "PRESENT") } @@ -233,6 +236,13 @@ fun MobileActsScreen( HorizontalDivider() Text("Finalizar contenido", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) Text("Al cerrar el contenido, el Acta queda inmutable y pasa a Para firmar. La firma del inspector se toma de Mi perfil del Dashboard; en la tablet firma el acompañante de la empresa.") + OutlinedTextField( + actNarrative, + { actNarrative = it }, + label = { Text("Descripción de lo actuado *") }, + minLines = 4, + modifier = Modifier.fillMaxWidth(), + ) Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { AssistChip( onClick = { closingUrgency = "NON_URGENT" }, @@ -244,8 +254,8 @@ fun MobileActsScreen( ) } Button( - onClick = { model.prepareSelectedAct(closingUrgency) }, - enabled = !model.busy && closure?.responsible != null && closingUrgency.isNotBlank(), + onClick = { model.prepareSelectedAct(closingUrgency, actNarrative) }, + enabled = !model.busy && closure?.responsible != null && closingUrgency.isNotBlank() && actNarrative.trim().length in 10..20000, modifier = Modifier.fillMaxWidth(), ) { Text("Cerrar contenido y pasar a firma") } } diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/ModernMobileActsScreen.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/ModernMobileActsScreen.kt index 68d3a81..9bde94b 100644 --- a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/ModernMobileActsScreen.kt +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/ModernMobileActsScreen.kt @@ -76,6 +76,9 @@ fun ModernMobileActsScreen( val scope = rememberCoroutineScope() var closingUrgency by rememberSaveable(selected?.id) { mutableStateOf("") } + var actNarrative by rememberSaveable(selected?.id) { + mutableStateOf(selected?.summary.takeUnless { it == null || it.startsWith("Se inició la inspección ") || it.startsWith("Acta de inspección en curso.") }.orEmpty()) + } var fullName by rememberSaveable(selected?.id) { mutableStateOf(representativeSeed?.fullName.orEmpty()) } var documentType by rememberSaveable(selected?.id) { mutableStateOf("DNI") } var documentNumber by rememberSaveable(selected?.id) { mutableStateOf(representativeSeed?.documentNumber.orEmpty()) } @@ -307,6 +310,14 @@ fun ModernMobileActsScreen( "Al cerrar el contenido, el Acta queda inmutable y pasa a Para firmar. La firma del inspector se toma de Mi perfil del Dashboard; en la tablet sólo firma el acompañante de la empresa. La urgencia pertenece al Acta completa. La urgencia no se asigna a cada Hallazgo.", color = MaterialTheme.colorScheme.onSurfaceVariant, ) + OutlinedTextField( + actNarrative, + { actNarrative = it }, + label = { Text("Descripción de lo actuado *") }, + supportingText = { Text("Relatá la inspección y sus observaciones. Se incorpora al acta antes de sellarla.") }, + minLines = 4, + modifier = Modifier.fillMaxWidth(), + ) Text("Urgencia del Acta", fontWeight = FontWeight.SemiBold) Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { FilterChip( @@ -330,8 +341,8 @@ fun ModernMobileActsScreen( color = MaterialTheme.colorScheme.onSurfaceVariant, ) Button( - onClick = { model.prepareSelectedAct(closingUrgency) }, - enabled = !model.busy && closure?.responsible != null && closingUrgency.isNotBlank(), + onClick = { model.prepareSelectedAct(closingUrgency, actNarrative) }, + enabled = !model.busy && closure?.responsible != null && closingUrgency.isNotBlank() && actNarrative.trim().length in 10..20000, modifier = Modifier.fillMaxWidth(), ) { Text("Cerrar contenido y pasar a firma") } } 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 50158d3..8d08121 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(38, BuildConfig.VERSION_CODE) - assertEquals("0.19.10-debug", BuildConfig.VERSION_NAME) + assertEquals(39, BuildConfig.VERSION_CODE) + assertEquals("0.19.11-debug", BuildConfig.VERSION_NAME) } @Test