android-finalizer: apply final field flow and Spanish UX

This commit is contained in:
DH Android Finalizer
2026-09-11 15:21:04 +00:00
parent 2d8425dec5
commit 8f051158c1
5 changed files with 59 additions and 39 deletions
@@ -241,6 +241,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
latitude: Double, latitude: Double,
longitude: Double, longitude: Double,
accuracyM: Double?, accuracyM: Double?,
description: String? = null,
) { ) {
val visitId = visit?.id ?: return val visitId = visit?.id ?: return
if (visit?.status != "IN_PROGRESS") { if (visit?.status != "IN_PROGRESS") {
@@ -258,6 +259,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
familyId = familyId, familyId = familyId,
name = name.trim(), name = name.trim(),
commonName = commonName?.trim()?.takeIf { it.isNotBlank() }, commonName = commonName?.trim()?.takeIf { it.isNotBlank() },
description = description?.trim()?.takeIf { it.isNotBlank() },
attributes = attributes, attributes = attributes,
deviceLatitude = latitude, deviceLatitude = latitude,
deviceLongitude = longitude, deviceLongitude = longitude,
@@ -305,6 +307,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
latitude: Double, latitude: Double,
longitude: Double, longitude: Double,
accuracyM: Double?, accuracyM: Double?,
description: String? = null,
) { ) {
val visitId = visit?.id ?: return val visitId = visit?.id ?: return
val asset = selectedFieldAsset?.asset ?: return val asset = selectedFieldAsset?.asset ?: return
@@ -480,12 +483,12 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
launchBusy { launchBusy {
actClosure = actsRepository.lock(actId) actClosure = actsRepository.lock(actId)
refreshSelectedActInternal(actId) refreshSelectedActInternal(actId)
notice = "Acta bloqueada. Su contenido quedó inmutable; ahora deben resolverse las firmas y manifestaciones." notice = "Acta cerrada y pendiente de firma. Su contenido quedó inmutable; las firmas y manifestaciones pueden completarse a continuación."
} }
} }
fun reopenSelectedAct() { fun reopenSelectedAct() {
error = "Un Acta bloqueada es inmutable y no puede volver a borrador." error = "Un Acta pendiente de firma es inmutable y no puede volver a edición."
} }
fun signSelectedActAsInspector( fun signSelectedActAsInspector(
@@ -493,6 +496,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
latitude: Double?, latitude: Double?,
longitude: Double?, longitude: Double?,
accuracyM: Double?, accuracyM: Double?,
description: String? = null,
) { ) {
val actId = selectedAct?.id ?: return val actId = selectedAct?.id ?: return
launchBusy { launchBusy {
@@ -546,7 +550,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
refreshSelectedActInternal(actId) refreshSelectedActInternal(actId)
loadActsInternal(currentVisit.id, selectDraft = false) loadActsInternal(currentVisit.id, selectDraft = false)
clearFindingState() clearFindingState()
notice = "${selectedAct?.code ?: "Acta"} SELLADA e inmutable. Podés crear otra Acta o continuar hacia el cierre de la Inspección." notice = "${selectedAct?.code ?: "Acta"} firmada y cerrada. Podés crear otra Acta o continuar hacia el cierre de la Inspección."
} }
} }
@@ -555,7 +559,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
launchBusy { launchBusy {
visit = actsRepository.closeVisit(visitId) visit = actsRepository.closeVisit(visitId)
loadVisitsInternal() loadVisitsInternal()
notice = "Inspección cerrada. Todas sus Actas quedaron SELLADAS y disponibles para el circuito de oficina." notice = "Inspección cerrada. Todas sus Actas quedaron firmadas y disponibles para el circuito de oficina."
} }
} }
@@ -210,7 +210,7 @@ fun FieldFindingScreen(model: MainViewModel) {
Card(Modifier.fillMaxWidth()) { Card(Modifier.fillMaxWidth()) {
Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
Text("${finding.code} · ${finding.title}", fontWeight = FontWeight.SemiBold) Text("${finding.code} · ${finding.title}", fontWeight = FontWeight.SemiBold)
Text("Gravedad: ${finding.severity ?: "s/d"} · ${finding.status}", style = MaterialTheme.typography.bodySmall) Text("Gravedad: ${finding.severity ?: "s/d"} · ${findingStatusLabelEs(finding.status)}", style = MaterialTheme.typography.bodySmall)
Text( Text(
"Evidencias: ${evidence.size} · Fotos: ${evidence.count { it.kind == "PHOTO" }}", "Evidencias: ${evidence.size} · Fotos: ${evidence.count { it.kind == "PHOTO" }}",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
@@ -3,11 +3,13 @@ package com.korexlabs.dhinspeccion.ui
import android.content.Context import android.content.Context
import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt import androidx.biometric.BiometricPrompt
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.filled.Visibility
@@ -29,7 +31,9 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.input.VisualTransformation
@@ -37,6 +41,7 @@ import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.korexlabs.dhinspeccion.MainViewModel import com.korexlabs.dhinspeccion.MainViewModel
import com.korexlabs.dhinspeccion.R
private const val BIOMETRIC_PREFS = "dh_v2_biometric" private const val BIOMETRIC_PREFS = "dh_v2_biometric"
private const val BIOMETRIC_ENABLED = "enabled" private const val BIOMETRIC_ENABLED = "enabled"
@@ -105,7 +110,13 @@ private fun EnhancedLoginScreen(model: MainViewModel, onPasswordLogin: () -> Uni
var passwordVisible by rememberSaveable { mutableStateOf(false) } var passwordVisible by rememberSaveable { mutableStateOf(false) }
Box(Modifier.fillMaxSize().padding(24.dp), contentAlignment = Alignment.Center) { Box(Modifier.fillMaxSize().padding(24.dp), contentAlignment = Alignment.Center) {
Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(14.dp)) { Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(14.dp), horizontalAlignment = Alignment.CenterHorizontally) {
Image(
painter = painterResource(R.drawable.mendoza_logo),
contentDescription = "Gobierno de Mendoza",
modifier = Modifier.fillMaxWidth().height(170.dp),
contentScale = ContentScale.Fit,
)
Text("DH Inspección", style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold) Text("DH Inspección", style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold)
Text("Aplicación de campo · Dirección de Hidrocarburos") Text("Aplicación de campo · Dirección de Hidrocarburos")
model.error?.let { Text(it, color = MaterialTheme.colorScheme.error) } model.error?.let { Text(it, color = MaterialTheme.colorScheme.error) }
@@ -113,7 +124,7 @@ private fun EnhancedLoginScreen(model: MainViewModel, onPasswordLogin: () -> Uni
OutlinedTextField( OutlinedTextField(
value = identifier, value = identifier,
onValueChange = { identifier = it }, onValueChange = { identifier = it },
label = { Text("Usuario o email") }, label = { Text("Usuario o correo electrónico") },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
singleLine = true, singleLine = true,
) )
@@ -198,6 +209,12 @@ private fun BiometricUnlockScreen(
verticalArrangement = Arrangement.spacedBy(14.dp), verticalArrangement = Arrangement.spacedBy(14.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Image(
painter = painterResource(R.drawable.mendoza_logo),
contentDescription = "Gobierno de Mendoza",
modifier = Modifier.fillMaxWidth().height(140.dp),
contentScale = ContentScale.Fit,
)
Text("DH Inspección", style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold) Text("DH Inspección", style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold)
Text("Ingresá con tu huella") Text("Ingresá con tu huella")
error?.let { Text(it, color = MaterialTheme.colorScheme.error) } error?.let { Text(it, color = MaterialTheme.colorScheme.error) }
@@ -245,7 +245,7 @@ fun ModernMobileActsScreen(
) { ) {
Text( Text(
if (newActUrgency == "URGENT") { if (newActUrgency == "URGENT") {
"El plazo urgente se computará desde el Acta conforme a la política institucional vigente al bloquearla." "El plazo urgente se computará desde el Acta conforme a la política institucional vigente al cerrar su contenido."
} else { } else {
"El plazo no urgente comienza con la oficialización GEDO, no al crear el Acta." "El plazo no urgente comienza con la oficialización GEDO, no al crear el Acta."
}, },
@@ -319,12 +319,12 @@ fun ModernMobileActsScreen(
OutlinedTextField(fullName, { fullName = it }, label = { Text("Nombre y apellido *") }, modifier = Modifier.fillMaxWidth(), singleLine = true) OutlinedTextField(fullName, { fullName = it }, label = { Text("Nombre y apellido *") }, modifier = Modifier.fillMaxWidth(), singleLine = true)
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(6.dp)) { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(6.dp)) {
listOf("DNI", "CUIL", "PASSPORT", "OTHER").forEach { kind -> listOf("DNI", "CUIL", "PASSPORT", "OTHER").forEach { kind ->
FilterChip(selected = documentType == kind, onClick = { documentType = kind }, label = { Text(kind) }) FilterChip(selected = documentType == kind, onClick = { documentType = kind }, label = { Text(documentTypeLabelEs(kind)) })
} }
} }
OutlinedTextField(documentNumber, { documentNumber = it }, label = { Text("Documento *") }, modifier = Modifier.fillMaxWidth(), singleLine = true) OutlinedTextField(documentNumber, { documentNumber = it }, label = { Text("Documento *") }, modifier = Modifier.fillMaxWidth(), singleLine = true)
OutlinedTextField(position, { position = it }, label = { Text("Cargo *") }, modifier = Modifier.fillMaxWidth(), singleLine = true) OutlinedTextField(position, { position = it }, label = { Text("Cargo *") }, modifier = Modifier.fillMaxWidth(), singleLine = true)
OutlinedTextField(email, { email = it }, label = { Text("Email") }, modifier = Modifier.fillMaxWidth(), singleLine = true) OutlinedTextField(email, { email = it }, label = { Text("Correo electrónico") }, modifier = Modifier.fillMaxWidth(), singleLine = true)
OutlinedTextField(phone, { phone = it }, label = { Text("Teléfono") }, modifier = Modifier.fillMaxWidth(), singleLine = true) OutlinedTextField(phone, { phone = it }, label = { Text("Teléfono") }, modifier = Modifier.fillMaxWidth(), singleLine = true)
Button( Button(
onClick = { model.setCompanyResponsiblePresent(fullName, documentType, documentNumber, position, email, phone) }, onClick = { model.setCompanyResponsiblePresent(fullName, documentType, documentNumber, position, email, phone) },
@@ -353,17 +353,17 @@ fun ModernMobileActsScreen(
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Filled.Lock, null, tint = MaterialTheme.colorScheme.error) Icon(Icons.Filled.Lock, null, tint = MaterialTheme.colorScheme.error)
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
Text("Finalizar contenido", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) Text("Cerrar Acta", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
} }
Text( Text(
"Al bloquear el Acta, su contenido y sus Hallazgos quedan inmutables.", "Al cerrar el contenido, el Acta queda inmutable y pasa a Pendiente de firma. Podés firmarla ahora o continuar con otra Acta.",
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
) )
Button( Button(
onClick = { model.prepareSelectedAct() }, onClick = { model.prepareSelectedAct() },
enabled = !model.busy && closure?.responsible != null, enabled = !model.busy && closure?.responsible != null,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) { Text("Finalizar y BLOQUEAR Acta") } ) { Text("Cerrar Acta y dejar pendiente de firma") }
} }
} }
} }
@@ -379,10 +379,10 @@ fun ModernMobileActsScreen(
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Filled.Lock, null, tint = MaterialTheme.colorScheme.secondary) Icon(Icons.Filled.Lock, null, tint = MaterialTheme.colorScheme.secondary)
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
Text("Acta BLOQUEADA", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) Text("Acta pendiente de firma", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
} }
Text("El contenido ya es inmutable. Restan firmas y manifestaciones para poder sellarla.") Text("El contenido ya está cerrado e inmutable. Completá la firma del inspector y la manifestación de la empresa cuando corresponda.")
closure?.closure?.preparedSha256?.let { Text("Hash · $it", style = MaterialTheme.typography.bodySmall) } closure?.closure?.preparedSha256?.let { Text("Huella de integridad · $it", style = MaterialTheme.typography.bodySmall) }
HorizontalDivider() HorizontalDivider()
Text("Firma del inspector", fontWeight = FontWeight.Bold) Text("Firma del inspector", fontWeight = FontWeight.Bold)
@@ -403,7 +403,7 @@ fun ModernMobileActsScreen(
val detail = when (companyOutcome.status) { val detail = when (companyOutcome.status) {
"SIGNED" -> if (companyOutcome.companyManifestation == "DISSENT") "Firma en disidencia" else "Firma en conformidad" "SIGNED" -> if (companyOutcome.companyManifestation == "DISSENT") "Firma en disidencia" else "Firma en conformidad"
"REFUSED" -> "Negativa a firmar" "REFUSED" -> "Negativa a firmar"
else -> companyOutcome.status else -> humanBackendLabelEs(companyOutcome.status)
} }
SuccessLine(detail) SuccessLine(detail)
companyOutcome.reason?.let { Text(it, style = MaterialTheme.typography.bodySmall) } companyOutcome.reason?.let { Text(it, style = MaterialTheme.typography.bodySmall) }
@@ -462,7 +462,7 @@ fun ModernMobileActsScreen(
) { ) {
Icon(Icons.Filled.CheckCircle, null) Icon(Icons.Filled.CheckCircle, null)
Spacer(Modifier.width(6.dp)) Spacer(Modifier.width(6.dp))
Text("SELLAR Acta definitivamente") Text("Firmar y cerrar Acta")
} }
} else { } else {
Text( Text(
@@ -480,8 +480,8 @@ fun ModernMobileActsScreen(
Row(Modifier.padding(18.dp), horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.Top) { Row(Modifier.padding(18.dp), horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.Top) {
Icon(Icons.Filled.CheckCircle, null, tint = MaterialTheme.colorScheme.primary) Icon(Icons.Filled.CheckCircle, null, tint = MaterialTheme.colorScheme.primary)
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(5.dp)) { Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(5.dp)) {
Text("Acta SELLADA e inmutable", fontWeight = FontWeight.Bold) Text("Acta firmada y cerrada", fontWeight = FontWeight.Bold)
Text("Desde este sellado se genera el PDF del Acta y el INF Word editable para el Inspector.") Text("Desde este cierre se genera el PDF del Acta y el informe INF editable (DOCX) para el Inspector.")
closure?.closure?.finalSha256?.let { Text("SHA-256 · $it", style = MaterialTheme.typography.bodySmall) } closure?.closure?.finalSha256?.let { Text("SHA-256 · $it", style = MaterialTheme.typography.bodySmall) }
} }
} }
@@ -503,10 +503,10 @@ fun ModernMobileActsScreen(
Column(Modifier.padding(18.dp), verticalArrangement = Arrangement.spacedBy(9.dp)) { Column(Modifier.padding(18.dp), verticalArrangement = Arrangement.spacedBy(9.dp)) {
Text("Finalizar Inspección", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) Text("Finalizar Inspección", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
if (pendingActs.isEmpty()) { if (pendingActs.isEmpty()) {
SuccessLine("Todas las Actas están SELLADAS") SuccessLine("Todas las Actas están firmadas y cerradas")
} else { } else {
Text( Text(
"Falta SELLAR: ${pendingActs.joinToString { it.code }}", "Falta firmar y cerrar: ${pendingActs.joinToString { it.code }}",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.error, color = MaterialTheme.colorScheme.error,
) )
@@ -531,13 +531,7 @@ private fun SuccessLine(text: String) {
} }
} }
private fun modernActStatusLabel(status: String): String = when (status) { private fun modernActStatusLabel(status: String): String = actStatusLabelEs(status)
"DRAFT" -> "Borrador"
"LOCKED" -> "Bloqueada"
"SEALED" -> "Sellada"
"CANCELLED" -> "Cancelada"
else -> status
}
private fun hasModernActLocation(context: Context): Boolean = private fun hasModernActLocation(context: Context): Boolean =
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
@@ -296,6 +296,7 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit)
var parentLabel by rememberSaveable(visit.id) { mutableStateOf("") } var parentLabel by rememberSaveable(visit.id) { mutableStateOf("") }
var name by rememberSaveable(visit.id) { mutableStateOf("") } var name by rememberSaveable(visit.id) { mutableStateOf("") }
var commonName by rememberSaveable(visit.id) { mutableStateOf("") } var commonName by rememberSaveable(visit.id) { mutableStateOf("") }
var description by rememberSaveable(visit.id) { mutableStateOf("") }
var selectedTypeId by rememberSaveable(visit.id) { mutableStateOf<String?>(null) } var selectedTypeId by rememberSaveable(visit.id) { mutableStateOf<String?>(null) }
var selectedFamilyId by rememberSaveable(visit.id) { mutableStateOf<String?>(null) } var selectedFamilyId by rememberSaveable(visit.id) { mutableStateOf<String?>(null) }
var familySearch by rememberSaveable(visit.id) { mutableStateOf("") } var familySearch by rememberSaveable(visit.id) { mutableStateOf("") }
@@ -311,6 +312,7 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit)
fun resetForm() { fun resetForm() {
name = "" name = ""
commonName = "" commonName = ""
description = ""
selectedTypeId = null selectedTypeId = null
selectedFamilyId = null selectedFamilyId = null
familySearch = "" familySearch = ""
@@ -444,6 +446,7 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit)
familyId = selectedFamilyId, familyId = selectedFamilyId,
name = name, name = name,
commonName = commonName, commonName = commonName,
description = description,
attributes = buildModernAttributes(type, attributeValues), attributes = buildModernAttributes(type, attributeValues),
latitude = geo.latitude, latitude = geo.latitude,
longitude = geo.longitude, longitude = geo.longitude,
@@ -800,18 +803,26 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit)
OutlinedTextField( OutlinedTextField(
value = name, value = name,
onValueChange = { name = it }, onValueChange = { name = it },
label = { Text("Nombre o código visible *") }, label = { Text("Nombre técnico *") },
supportingText = { Text("Usá lo que figura en placa o identifica el elemento en campo.") }, supportingText = { Text("Usá la denominación técnica del elemento. El código DH se genera automáticamente.") },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
singleLine = true, singleLine = true,
) )
OutlinedTextField( OutlinedTextField(
value = commonName, value = commonName,
onValueChange = { commonName = it }, onValueChange = { commonName = it },
label = { Text("Nombre habitual") }, label = { Text("Nombre habitual (opcional)") },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
singleLine = true, singleLine = true,
) )
OutlinedTextField(
value = description,
onValueChange = { description = it },
label = { Text("Descripción (opcional)") },
supportingText = { Text("Dato equivalente al alta desde el Dashboard.") },
modifier = Modifier.fillMaxWidth(),
minLines = 2,
)
selectedType?.attributes?.forEach { definition -> selectedType?.attributes?.forEach { definition ->
OutlinedTextField( OutlinedTextField(
@@ -1073,13 +1084,7 @@ private fun modernNormalize(value: String): String = value.trim().lowercase()
.replace('ó', 'o').replace('í', 'i').replace('á', 'a').replace('é', 'e').replace('ú', 'u') .replace('ó', 'o').replace('í', 'i').replace('á', 'a').replace('é', 'e').replace('ú', 'u')
.replace("_", "").replace("-", "").replace(" ", "") .replace("_", "").replace("-", "").replace(" ", "")
private fun modernStatusLabel(status: String): String = when (status) { private fun modernStatusLabel(status: String): String = visitStatusLabelEs(status)
"PLANNED" -> "Planificada"
"IN_PROGRESS" -> "En curso"
"CLOSED" -> "Cerrada"
"CANCELLED" -> "Cancelada"
else -> status
}
private fun buildModernAttributes(type: FieldType, values: Map<String, String>): Map<String, Any?> = private fun buildModernAttributes(type: FieldType, values: Map<String, String>): Map<String, Any?> =
type.attributes.mapNotNull { definition -> type.attributes.mapNotNull { definition ->