Scan to open on your phone
Point a camera at the QR code - it opens this exact paste, no app needed.
https://codepastes.com/zzgs7wv52kjft60euntitledPlain Text
package com.negocio.desinfectantes.ui.ventas
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.negocio.desinfectantes.data.*
import java.text.NumberFormat
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
private val FondoHistorial = Color(0xFF101D35)
private val TarjetaHistorial = Color(0xFF1B2D4A)
private val AzulHistorial = Color(0xFF2585F5)
private val TextoHistorial = Color.White
private val TextoSecundario = Color(0xFFA9BEDB)
private fun formatoDinero(valor: Double): String {
return NumberFormat.getCurrencyInstance(
Locale("es", "CO")
).format(valor)
}
private fun formatoFecha(fecha: Long): String {
return SimpleDateFormat(
"dd/MM/yyyy - hh:mm a",
Locale("es", "CO")
).format(Date(fecha))
}
@Composable
fun HistorialVentasScreen(
modifier: Modifier = Modifier,
ventas: List<VentaEntity>,
clientes: List<ClienteEntity>,
productos: List<ProductoEntity>,
aromas: List<AromaEntity>,
presentaciones: List<PresentacionEntity>,
productoDao: ProductoDao,
onVolver: () -> Unit,
ventaInicialId: String? = null
) {
var ventaSeleccionada by remember {
mutableStateOf<VentaEntity?>(null)
}
LaunchedEffect(ventaInicialId, ventas) {
if (ventaInicialId != null) {
ventaSeleccionada = ventas.find {
it.id == ventaInicialId
}
}
}
LaunchedEffect(ventas, ventaSeleccionada?.id) {
val idActual = ventaSeleccionada?.id
if (idActual != null) {
ventaSeleccionada = ventas.find {
it.id == idActual
}
}
}
val scope = rememberCoroutineScope()
var montoAbonoTexto by remember { mutableStateOf("") }
var metodoAbono by remember { mutableStateOf("Efectivo") }
var mensajeAbono by remember { mutableStateOf<String?>(null) }
var guardandoAbono by remember { mutableStateOf(false) }
var procesandoPedido by remember {
mutableStateOf(false)
}
var mensajePedido by remember {
mutableStateOf<String?>(null)
}
var errorPedido by remember {
mutableStateOf(false)
}
var mostrarDialogoCancelar by remember {
mutableStateOf(false)
}
val detallesFlow = remember(ventaSeleccionada?.id) {
ventaSeleccionada?.let {
productoDao.obtenerDetallesVenta(it.id)
} ?: flowOf(emptyList<DetalleVentaEntity>())
}
val pagosFlow = remember(ventaSeleccionada?.id) {
ventaSeleccionada?.let {
productoDao.obtenerPagosVenta(it.id)
} ?: flowOf(emptyList<PagoVentaEntity>())
}
val detalles by detallesFlow.collectAsState(
initial = emptyList()
)
val pagos by pagosFlow.collectAsState(
initial = emptyList()
)
Column(
modifier = modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
if (ventaSeleccionada == null) {
Text(
text = "Historial de ventas",
color = TextoHistorial,
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold
)
Text(
text = "${ventas.size} venta(s) registrada(s)",
color = TextoSecundario
)
if (ventas.isEmpty()) {
Card(
colors = CardDefaults.cardColors(
containerColor = TarjetaHistorial
),
modifier = Modifier.fillMaxWidth()
) {
Text(
text = "Todavía no hay ventas registradas.",
color = TextoSecundario,
modifier = Modifier.padding(20.dp)
)
}
} else {
ventas.forEach { venta ->
val cliente = clientes.find {
it.id == venta.clienteId
}
Card(
modifier = Modifier
.fillMaxWidth()
.clickable {
ventaSeleccionada = venta
},
colors = CardDefaults.cardColors(
containerColor = TarjetaHistorial
)
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(7.dp)
) {
Text(
text = cliente?.nombre
?: "Cliente no disponible",
color = TextoHistorial,
fontWeight = FontWeight.Bold
)
Text(
text = formatoFecha(venta.fechaCreacion),
color = TextoSecundario
)
Text(
text = "Sede: ${venta.sede}",
color = TextoSecundario
)
Text(
text = "Tipo: ${venta.tipoVenta.replace("_", " ")}",
color = TextoSecundario
)
Text(
text = "Entrega: ${venta.estadoEntrega.replace("_", " ")}",
color = TextoSecundario
)
Text(
text = formatoDinero(venta.total),
color = Color(0xFF81C784),
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.titleMedium
)
Text(
text = "Toca para ver los detalles →",
color = AzulHistorial
)
}
}
}
}
} else {
val venta = ventaSeleccionada!!
val cliente = clientes.find {
it.id == venta.clienteId
}
val totalPagado = pagos.sumOf { it.monto }
val saldoPendiente = (
venta.total - totalPagado
).coerceAtLeast(0.0)
Text(
text = "Detalle de la venta",
color = TextoHistorial,
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold
)
OutlinedButton(
onClick = {
ventaSeleccionada = null
}
) {
Text("← Volver al historial")
}
Card(
colors = CardDefaults.cardColors(
containerColor = TarjetaHistorial
),
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = cliente?.nombre
?: "Cliente no disponible",
color = TextoHistorial,
fontWeight = FontWeight.Bold
)
Text(
text = formatoFecha(venta.fechaCreacion),
color = TextoSecundario
)
Text(
text = "Sede: ${venta.sede}",
color = TextoSecundario
)
Text(
text = "Tipo: ${venta.tipoVenta.replace("_", " ")}",
color = TextoSecundario
)
Text(
text = "Estado: ${venta.estadoEntrega.replace("_", " ")}",
color = TextoSecundario
)
}
}
Text(
text = "Productos vendidos",
color = TextoHistorial,
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.titleMedium
)
detalles.forEach { detalle ->
val presentacion = presentaciones.find {
it.id == detalle.presentacionId
}
val aroma = presentacion?.let { p ->
aromas.find { it.id == p.aromaId }
}
val producto = aroma?.let { a ->
productos.find { it.id == a.productoId }
}
Card(
colors = CardDefaults.cardColors(
containerColor = TarjetaHistorial
),
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(14.dp),
verticalArrangement = Arrangement.spacedBy(5.dp)
) {
Text(
text = producto?.nombre
?: "Producto no disponible",
color = TextoHistorial,
fontWeight = FontWeight.Bold
)
Text(
text = "Aroma: ${aroma?.nombre ?: "No disponible"}",
color = TextoSecundario
)
Text(
text = "Presentación: ${presentacion?.nombre ?: "No disponible"}",
color = TextoSecundario
)
Text(
text = "Cantidad: ${detalle.cantidad}",
color = TextoSecundario
)
Text(
text = "Precio unitario: ${formatoDinero(detalle.precioUnitario)}",
color = TextoSecundario
)
Text(
text = "Subtotal: ${formatoDinero(detalle.subtotal)}",
color = Color(0xFF81C784),
fontWeight = FontWeight.Bold
)
}
}
}
// CONTROL DE ESTADOS DE PEDIDOS
if (venta.estadoEntrega == "PENDIENTE_FABRICACION") {
Text(
text = "Gestión del pedido",
color = TextoHistorial,
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.titleMedium
)
Text(
text = "Cuando tengas los productos fabricados y distribuidos, puedes reservar las unidades para este cliente.",
color = TextoSecundario
)
Button(
onClick = {
procesandoPedido = true
mensajePedido = null
errorPedido = false
scope.launch {
try {
productoDao.reservarPedidoParaEntrega(venta.id)
mensajePedido =
"¡Pedido listo! Las unidades quedaron reservadas."
} catch (e: Exception) {
mensajePedido = e.message
?: "No fue posible reservar las unidades."
errorPedido = true
} finally {
procesandoPedido = false
}
}
},
enabled = !procesandoPedido,
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(
containerColor = AzulHistorial
)
) {
Text(
if (procesandoPedido)
"Reservando unidades..."
else
"Marcar como listo para entregar"
)
}
} else if (venta.estadoEntrega == "PENDIENTE_ENTREGA") {
Text(
text = "Pedido listo para entregar",
color = Color(0xFF81C784),
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.titleMedium
)
Text(
text = "Las unidades están reservadas para este cliente. Confirma cuando hayas realizado la entrega.",
color = TextoSecundario
)
Button(
onClick = {
procesandoPedido = true
mensajePedido = null
errorPedido = false
scope.launch {
try {
productoDao.entregarPedidoReservado(venta.id)
mensajePedido =
"¡Entrega registrada correctamente!"
} catch (e: Exception) {
mensajePedido = e.message
?: "No fue posible registrar la entrega."
errorPedido = true
} finally {
procesandoPedido = false
}
}
},
enabled = !procesandoPedido,
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFF2E9D65)
)
) {
Text(
if (procesandoPedido)
"Registrando entrega..."
else
"Confirmar entrega"
)
}
}
// Botón para cancelar pedidos que aún no han sido entregados.
if (
venta.estadoEntrega == "PENDIENTE_FABRICACION" ||
venta.estadoEntrega == "PENDIENTE_ENTREGA"
) {
OutlinedButton(
onClick = {
mostrarDialogoCancelar = true
mensajePedido = null
errorPedido = false
},
enabled = !procesandoPedido,
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.outlinedButtonColors(
contentColor = Color(0xFFFF6B6B)
)
) {
Text("Cancelar pedido")
}
}
if (mostrarDialogoCancelar) {
AlertDialog(
onDismissRequest = {
if (!procesandoPedido) {
mostrarDialogoCancelar = false
}
},
title = {
Text("Cancelar pedido")
},
text = {
Text(
if (venta.estadoEntrega == "PENDIENTE_ENTREGA") {
"¿Seguro que deseas cancelar este pedido? " +
"Las unidades reservadas volverán a estar disponibles."
} else {
"¿Seguro que deseas cancelar este pedido? " +
"Como aún no está listo para entregar, no se modificará el inventario."
}
)
},
confirmButton = {
TextButton(
onClick = {
mostrarDialogoCancelar = false
procesandoPedido = true
mensajePedido = null
errorPedido = false
scope.launch {
try {
productoDao.cancelarPedido(venta.id)
mensajePedido =
"Pedido cancelado correctamente."
} catch (e: Exception) {
mensajePedido = e.message
?: "No fue posible cancelar el pedido."
errorPedido = true
} finally {
procesandoPedido = false
}
}
},
enabled = !procesandoPedido
) {
Text(
"Sí, cancelar",
color = Color(0xFFFF6B6B)
)
}
},
dismissButton = {
TextButton(
onClick = {
mostrarDialogoCancelar = false
},
enabled = !procesandoPedido
) {
Text("No, volver")
}
}
)
}
if (mensajePedido != null) {
Text(
text = mensajePedido!!,
color = if (errorPedido)
Color(0xFFFF6B6B)
else
Color(0xFF81C784),
fontWeight = FontWeight.Bold
)
}
Text(
text = "Pagos registrados",
color = TextoHistorial,
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.titleMedium
)
if (pagos.isEmpty()) {
Text(
text = "Esta venta todavía no tiene pagos registrados.",
color = TextoSecundario
)
} else {
pagos.forEach { pago ->
Card(
colors = CardDefaults.cardColors(
containerColor = TarjetaHistorial
),
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(14.dp),
verticalArrangement = Arrangement.spacedBy(5.dp)
) {
Text(
text = formatoDinero(pago.monto),
color = Color(0xFF81C784),
fontWeight = FontWeight.Bold
)
Text(
text = "Método: ${pago.metodoPago}",
color = TextoSecundario
)
Text(
text = formatoFecha(pago.fechaCreacion),
color = TextoSecundario
)
}
}
}
}
Card(
colors = CardDefaults.cardColors(
containerColor = TarjetaHistorial
),
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "Resumen de la venta",
color = TextoHistorial,
fontWeight = FontWeight.Bold
)
Text(
text = "Total: ${formatoDinero(venta.total)}",
color = TextoHistorial
)
Text(
text = "Total pagado: ${formatoDinero(totalPagado)}",
color = Color(0xFF81C784)
)
Text(
text = "Saldo pendiente: ${formatoDinero(saldoPendiente)}",
color = if (saldoPendiente > 0)
Color(0xFFFFB74D)
else
Color(0xFF81C784),
fontWeight = FontWeight.Bold
)
}
}
if (saldoPendiente > 0) {
Text(
text = "Registrar un abono",
color = TextoHistorial,
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.titleMedium
)
OutlinedTextField(
value = montoAbonoTexto,
onValueChange = { montoAbonoTexto = it },
label = { Text("Valor del abono") },
placeholder = { Text("Ej. 50000") },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
OutlinedTextField(
value = metodoAbono,
onValueChange = { metodoAbono = it },
label = { Text("Método de pago") },
placeholder = { Text("Ej. Efectivo, Nequi, Bancolombia") },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
if (mensajeAbono != null) {
Text(
text = mensajeAbono!!,
color = Color(0xFFFF6B6B),
fontWeight = FontWeight.Bold
)
}
Button(
onClick = {
val monto = montoAbonoTexto
.replace(",", ".")
.toDoubleOrNull()
when {
monto == null || monto <= 0 -> {
mensajeAbono = "Ingresa un valor de abono válido."
}
monto > saldoPendiente -> {
mensajeAbono = "El abono no puede superar el saldo pendiente."
}
metodoAbono.isBlank() -> {
mensajeAbono = "Especifica el método de pago."
}
else -> {
mensajeAbono = null
guardandoAbono = true
scope.launch {
try {
productoDao.guardarPagoVenta(
PagoVentaEntity(
id = java.util.UUID.randomUUID().toString(),
ventaId = venta.id,
monto = monto,
metodoPago = metodoAbono.trim(),
fechaCreacion = System.currentTimeMillis()
)
)
montoAbonoTexto = ""
mensajeAbono = "¡Abono registrado correctamente!"
} catch (e: Exception) {
mensajeAbono = e.message
?: "No fue posible registrar el abono."
} finally {
guardandoAbono = false
}
}
}
}
},
enabled = !guardandoAbono,
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(
containerColor = AzulHistorial
)
) {
Text(
if (guardandoAbono) "Guardando abono..." else "Guardar abono"
)
}
}
}
}
}26,316 chars · codepastes.com