se agrega los requerimientos de la LODPD con firma de documentos

This commit is contained in:
2026-04-07 15:14:44 -05:00
parent 408a9b4435
commit 46a11728b8
31 changed files with 1432 additions and 52 deletions
@@ -16,8 +16,15 @@ interface LinkedSignApiService {
@POST("auth/login")
suspend fun login(@Body body: LoginRequest): ApiEnvelope<LoginData>
@Multipart
@POST("auth/register")
suspend fun register(@Body body: RegisterRequest): ApiEnvelope<RegisterData>
suspend fun register(
@Part("email") email: RequestBody,
@Part("password") password: RequestBody,
@Part("certPassword") certPassword: RequestBody,
@Part("consentAccepted") consentAccepted: RequestBody,
@Part certificateFile: MultipartBody.Part
): ApiEnvelope<RegisterData>
@POST("auth/refresh")
suspend fun refresh(@Body body: RefreshRequest): ApiEnvelope<RefreshData>
@@ -79,9 +79,16 @@ class LinkedSignRepository(private val context: Context) {
}
}
suspend fun register(email: String, password: String): RegisterData {
suspend fun register(email: String, password: String, certPassword: String, certificateUri: Uri, certificateName: String): RegisterData {
return execute {
unwrap(service.register(RegisterRequest(email.trim(), password)))
val filePart = withContext(Dispatchers.IO) {
createFilePart(certificateUri, certificateName, "application/octet-stream", "certificateFile")
}
val emailBody = email.trim().toRequestBody("text/plain".toMediaType())
val passwordBody = password.toRequestBody("text/plain".toMediaType())
val certPasswordBody = certPassword.toRequestBody("text/plain".toMediaType())
val consentBody = "true".toRequestBody("text/plain".toMediaType())
unwrap(service.register(emailBody, passwordBody, certPasswordBody, consentBody, filePart))
}
}
@@ -43,11 +43,6 @@ data class LoginData(
val user: SessionUser
)
data class RegisterRequest(
val email: String,
val password: String
)
data class RegisterData(
val message: String?,
val userId: Int?
@@ -368,9 +368,9 @@ fun LinkedSignApp(viewModel: MainViewModel, appContext: Context, activity: Compo
}
}
},
onRegister = { email, password ->
onRegister = { email, password, certPassword, certificateUri, certificateName ->
scope.launch {
runCatching { viewModel.register(email, password) }
runCatching { viewModel.register(email, password, certPassword, certificateUri, certificateName) }
.onSuccess {
authMode = AuthMode.VerifyNotice
}
@@ -529,7 +529,7 @@ private fun AuthFlow(
verifyEmail: String?,
onConsumeVerifyNotice: () -> Unit,
onLogin: (String, String) -> Unit,
onRegister: (String, String) -> Unit,
onRegister: (String, String, String, Uri, String) -> Unit,
modifier: Modifier = Modifier
) {
Column(
@@ -780,11 +780,25 @@ private fun LoginCard(onSubmit: (String, String) -> Unit, onGoRegister: () -> Un
}
@Composable
private fun RegisterCard(onSubmit: (String, String) -> Unit, onGoLogin: () -> Unit) {
private fun RegisterCard(onSubmit: (String, String, String, Uri, String) -> Unit, onGoLogin: () -> Unit) {
val context = LocalContext.current
var email by rememberSaveable { mutableStateOf("") }
var password by rememberSaveable { mutableStateOf("") }
var confirmPassword by rememberSaveable { mutableStateOf("") }
var certPassword by rememberSaveable { mutableStateOf("") }
var consentAccepted by rememberSaveable { mutableStateOf(false) }
var certificateUri by rememberSaveable { mutableStateOf<Uri?>(null) }
var certificateName by rememberSaveable { mutableStateOf("") }
val passwordProblems = passwordIssues(password)
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
uri ?: return@rememberLauncherForActivityResult
try {
context.contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
} catch (_: Exception) {
}
certificateUri = uri
certificateName = queryFileName(context, uri) ?: "certificado.p12"
}
ElevatedCard(
shape = RoundedCornerShape(24.dp),
@@ -810,7 +824,7 @@ private fun RegisterCard(onSubmit: (String, String) -> Unit, onGoLogin: () -> Un
}
Column {
Text("Alta protegida", color = BrandDark, fontWeight = FontWeight.SemiBold, fontSize = AppFontSizes.base)
Text("Usa un correo válido y una clave fuerte", color = TextMuted, fontSize = AppFontSizes.sm)
Text("Correo válido, clave fuerte y certificado para el consentimiento legal", color = TextMuted, fontSize = AppFontSizes.sm)
}
}
}
@@ -822,6 +836,38 @@ private fun RegisterCard(onSubmit: (String, String) -> Unit, onGoLogin: () -> Un
AppTextField(value = password, onValueChange = { password = it }, label = "Contraseña", isPassword = true)
PasswordChecklist(password = password)
AppTextField(value = confirmPassword, onValueChange = { confirmPassword = it }, label = "Confirmar contraseña", isPassword = true)
AppTextField(value = certPassword, onValueChange = { certPassword = it }, label = "Contraseña del certificado", isPassword = true)
Surface(color = SurfaceAlt, shape = RoundedCornerShape(14.dp), border = BorderStroke(1.dp, StrokeSoft)) {
Column(modifier = Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) {
Text("Registro legal inicial", color = BrandDark, fontWeight = FontWeight.SemiBold, fontSize = AppFontSizes.base)
Text(
"Debes cargar tu P12/PFX. La contraseña del certificado solo se usará para firmar los documentos legales del primer registro y no se guardará.",
color = TextMuted,
fontSize = AppFontSizes.sm
)
Row(horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically) {
OutlinedActionButton(
label = if (certificateName.isBlank()) "Seleccionar P12/PFX" else certificateName,
icon = Icons.Outlined.Badge,
modifier = Modifier.weight(1f)
) {
launcher.launch(arrayOf("*/*"))
}
}
Row(verticalAlignment = Alignment.Top, horizontalArrangement = Arrangement.spacedBy(10.dp)) {
androidx.compose.material3.Checkbox(
checked = consentAccepted,
onCheckedChange = { consentAccepted = it }
)
Text(
"Acepto los términos de uso, la política de privacidad y el consentimiento expreso para tratamiento de datos y uso de firma electrónica.",
color = TextMuted,
fontSize = AppFontSizes.sm,
modifier = Modifier.weight(1f)
)
}
}
}
if (passwordProblems.isNotEmpty() && password.isNotBlank()) {
Surface(color = WarningBg, shape = RoundedCornerShape(10.dp)) {
Text("La contraseña aún no cumple todos los requisitos.", color = Warning, modifier = Modifier.padding(10.dp), fontSize = AppFontSizes.md)
@@ -838,7 +884,10 @@ private fun RegisterCard(onSubmit: (String, String) -> Unit, onGoLogin: () -> Un
when {
passwordProblems.isNotEmpty() -> Unit
password != confirmPassword -> Unit
else -> onSubmit(email, password)
certPassword.isBlank() -> Unit
certificateUri == null -> Unit
!consentAccepted -> Unit
else -> onSubmit(email, password, certPassword, certificateUri!!, certificateName.ifBlank { "certificado.p12" })
}
},
modifier = Modifier.fillMaxWidth().height(50.dp),
@@ -63,8 +63,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
uiState = buildState(session, cache, loading = false)
}
suspend fun register(email: String, password: String) {
repository.register(email, password)
suspend fun register(email: String, password: String, certPassword: String, certificateUri: Uri, certificateName: String) {
repository.register(email, password, certPassword, certificateUri, certificateName)
uiState = uiState.copy(emailForVerificationNotice = email)
}