feat(android): completar alta de Inventario de campo en castellano

This commit is contained in:
2026-09-11 11:30:22 -03:00
parent 752bd4d6db
commit 9ace5b8e46
@@ -262,7 +262,7 @@ private fun ModernChecklistCard(visit: VisitDetail) {
val checklist = visit.checklist val checklist = visit.checklist
ElevatedCard(Modifier.fillMaxWidth()) { ElevatedCard(Modifier.fillMaxWidth()) {
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
Text("Antecedentes", fontWeight = FontWeight.Bold) Text("Antecedentes y controles", fontWeight = FontWeight.Bold)
Text( Text(
"${checklist.antecedents} antecedentes · ${checklist.upcomingControls} próximos controles", "${checklist.antecedents} antecedentes · ${checklist.upcomingControls} próximos controles",
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -274,7 +274,7 @@ private fun ModernChecklistCard(visit: VisitDetail) {
) )
} }
if (checklist.stale) { if (checklist.stale) {
Text("El checklist requiere actualización.", color = MaterialTheme.colorScheme.error) Text("La lista de control requiere actualización.", color = MaterialTheme.colorScheme.error)
} }
} }
} }
@@ -296,6 +296,8 @@ 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 discoveryNotes 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 +313,8 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit)
fun resetForm() { fun resetForm() {
name = "" name = ""
commonName = "" commonName = ""
description = ""
discoveryNotes = ""
selectedTypeId = null selectedTypeId = null
selectedFamilyId = null selectedFamilyId = null
familySearch = "" familySearch = ""
@@ -427,7 +431,7 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit)
pendingPhotoGeo = geo pendingPhotoGeo = geo
takePicture.launch(uri) takePicture.launch(uri)
} }
.onFailure { localError = it.message ?: "No se pudo obtener el GPS para la fotografía." } .onFailure { localError = it.message ?: "No se pudo obtener la ubicación para la fotografía." }
} }
} }
@@ -444,13 +448,15 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit)
familyId = selectedFamilyId, familyId = selectedFamilyId,
name = name, name = name,
commonName = commonName, commonName = commonName,
description = description,
discoveryNotes = discoveryNotes,
attributes = buildModernAttributes(type, attributeValues), attributes = buildModernAttributes(type, attributeValues),
latitude = geo.latitude, latitude = geo.latitude,
longitude = geo.longitude, longitude = geo.longitude,
accuracyM = geo.accuracyM, accuracyM = geo.accuracyM,
) )
} }
.onFailure { localError = it.message ?: "No se pudo capturar la ubicación GPS." } .onFailure { localError = it.message ?: "No se pudo capturar la ubicación actual." }
} }
} }
@@ -800,8 +806,8 @@ 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 / identificación *") },
supportingText = { Text("Usá lo que figura en placa o identifica el elemento en campo.") }, supportingText = { Text("Usá lo que figura en placa o identifica el elemento. El Código DH de campo se genera automáticamente.") },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
singleLine = true, singleLine = true,
) )
@@ -812,6 +818,21 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit)
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
singleLine = true, singleLine = true,
) )
OutlinedTextField(
value = description,
onValueChange = { description = it },
label = { Text("Descripción (opcional)") },
modifier = Modifier.fillMaxWidth(),
minLines = 2,
)
OutlinedTextField(
value = discoveryNotes,
onValueChange = { discoveryNotes = it },
label = { Text("Observaciones del alta (opcional)") },
supportingText = { Text("Anotá datos útiles para la revisión posterior en oficina.") },
modifier = Modifier.fillMaxWidth(),
minLines = 2,
)
selectedType?.attributes?.forEach { definition -> selectedType?.attributes?.forEach { definition ->
OutlinedTextField( OutlinedTextField(
@@ -830,12 +851,12 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit)
} }
HorizontalDivider() HorizontalDivider()
ModernStepHeader(if (isSubinstallation) 3 else 2, "GPS + foto") ModernStepHeader(if (isSubinstallation) 3 else 2, "Ubicación y foto")
Surface(Modifier.fillMaxWidth(), shape = MaterialTheme.shapes.medium, color = MaterialTheme.colorScheme.secondaryContainer) { Surface(Modifier.fillMaxWidth(), shape = MaterialTheme.shapes.medium, color = MaterialTheme.colorScheme.secondaryContainer) {
Row(Modifier.padding(14.dp), horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically) { Row(Modifier.padding(14.dp), horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Filled.CameraAlt, null, tint = MaterialTheme.colorScheme.secondary) Icon(Icons.Filled.CameraAlt, null, tint = MaterialTheme.colorScheme.secondary)
Text( Text(
"Al guardar capturamos el GPS y abrimos la cámara. La foto es obligatoria antes de registrar Hallazgos.", "Al guardar capturamos la ubicación y abrimos la cámara. La foto es obligatoria antes de registrar Hallazgos.",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
) )
@@ -927,7 +948,7 @@ private fun ModernInventoryCard(item: FieldInventoryItem, onInspect: () -> Unit,
} }
item.commonName?.takeIf { it.isNotBlank() }?.let { Text(it, style = MaterialTheme.typography.bodySmall) } item.commonName?.takeIf { it.isNotBlank() }?.let { Text(it, style = MaterialTheme.typography.bodySmall) }
Text( Text(
if (item.readyForFinding) "Listo para Hallazgos" else "Falta GPS / foto", if (item.readyForFinding) "Listo para Hallazgos" else "Falta ubicación / foto",
color = if (item.readyForFinding) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error, color = if (item.readyForFinding) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
@@ -966,7 +987,7 @@ private fun ModernCaptureCard(
IconButton(onClick = onClose) { Icon(Icons.Filled.Close, "Cerrar") } IconButton(onClick = onClose) { Icon(Icons.Filled.Close, "Cerrar") }
} }
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
Text(if (gps) "GPS" else "GPS pendiente", color = if (gps) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error) Text(if (gps) "Ubicación" else "Ubicación pendiente", color = if (gps) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error)
Text("Fotos · $photos") Text("Fotos · $photos")
} }
if (!ready) { if (!ready) {
@@ -1078,7 +1099,7 @@ private fun modernStatusLabel(status: String): String = when (status) {
"IN_PROGRESS" -> "En curso" "IN_PROGRESS" -> "En curso"
"CLOSED" -> "Cerrada" "CLOSED" -> "Cerrada"
"CANCELLED" -> "Cancelada" "CANCELLED" -> "Cancelada"
else -> status else -> "Estado no reconocido"
} }
private fun buildModernAttributes(type: FieldType, values: Map<String, String>): Map<String, Any?> = private fun buildModernAttributes(type: FieldType, values: Map<String, String>): Map<String, Any?> =
@@ -1112,7 +1133,7 @@ private suspend fun currentModernGeo(context: Context): ModernGeoSnapshot = susp
client.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, source.token) client.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, source.token)
.addOnSuccessListener { location -> .addOnSuccessListener { location ->
if (!continuation.isActive) return@addOnSuccessListener if (!continuation.isActive) return@addOnSuccessListener
if (location == null) continuation.resumeWithException(IllegalStateException("No se pudo obtener una ubicación GPS actual.")) if (location == null) continuation.resumeWithException(IllegalStateException("No se pudo obtener una ubicación actual."))
else continuation.resume(ModernGeoSnapshot(location.latitude, location.longitude, location.accuracy.toDouble())) else continuation.resume(ModernGeoSnapshot(location.latitude, location.longitude, location.accuracy.toDouble()))
} }
.addOnFailureListener { if (continuation.isActive) continuation.resumeWithException(it) } .addOnFailureListener { if (continuation.isActive) continuation.resumeWithException(it) }