Scan to open on your phone
Point a camera at the QR code - it opens this exact paste, no app needed.
https://codepastes.com/zzgs9hfsgq9p8adksin-tituloPlain Text
package com.negocio.desinfectantes.ui.produccion
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.background
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.Alignment
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import com.negocio.desinfectantes.data.*
private val Navy = Color(0xFF101D35)
private val CardBlue = Color(0xFF1B2D4A)
private val AccentBlue = Color(0xFF2585F5)
private val SoftBlue = Color(0xFFA9BEDB)
@Composable
fun ProduccionScreen(
modifier: Modifier = Modifier,
productos: List<ProductoEntity>,
aromas: List<AromaEntity>,
presentaciones: List<PresentacionEntity>,
onVolver: () -> Unit,
onGuardar: (PresentacionEntity, Int, Int, Int) -> Unit
) {
var productoSeleccionado by remember {
mutableStateOf<ProductoEntity?>(null)
}
var aromaSeleccionado by remember {
mutableStateOf<AromaEntity?>(null)
}
var presentacionSeleccionada by remember {
mutableStateOf<PresentacionEntity?>(null)
}
var cantidadProducida by remember { mutableStateOf("") }
var cantidadBarrancas by remember { mutableStateOf("") }
var cantidadValledupar by remember { mutableStateOf("") }
var menuProducto by remember { mutableStateOf(false) }
var menuAroma by remember { mutableStateOf(false) }
var menuPresentacion by remember { mutableStateOf(false) }
val aromasDisponibles = aromas.filter {
it.productoId == productoSeleccionado?.id && it.activo
}
val presentacionesDisponibles = presentaciones.filter {
it.aromaId == aromaSeleccionado?.id && it.activo
}
val producidas = cantidadProducida.toIntOrNull()
val barrancas = cantidadBarrancas.toIntOrNull()
val valledupar = cantidadValledupar.toIntOrNull()
val totalDistribuido =
(barrancas ?: 0) + (valledupar ?: 0)
val datosCompletos =
presentacionSeleccionada != null &&
producidas != null && producidas > 0 &&
barrancas != null && valledupar != null &&
barrancas >= 0 && valledupar >= 0
val distribucionCorrecta =
datosCompletos && totalDistribuido == producidas
val mensajeValidacion = when {
cantidadProducida.isNotEmpty() && (producidas == null || producidas <= 0) ->
"Escribe una cantidad fabricada válida."
cantidadBarrancas.isNotEmpty() && barrancas == null ->
"La cantidad de Barrancas no es válida."
cantidadValledupar.isNotEmpty() && valledupar == null ->
"La cantidad de Valledupar no es válida."
datosCompletos && totalDistribuido > producidas!! ->
"Has distribuido ${totalDistribuido - producidas} unidades de más."
datosCompletos && totalDistribuido < producidas!! ->
"Faltan ${producidas - totalDistribuido} unidades por distribuir."
distribucionCorrecta ->
null
else ->
null
}
Column(
modifier = modifier
.fillMaxSize()
.background(Navy)
.verticalScroll(rememberScrollState())
.padding(20.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
TextButton(onClick = onVolver) {
Text("← Volver", color = SoftBlue)
}
}
Text(
"Nueva producción",
color = Color.White,
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(8.dp))
Text(
"Registra lo que fabricaste y distribuye las unidades entre tus sedes.",
color = SoftBlue,
style = MaterialTheme.typography.bodyMedium
)
Spacer(modifier = Modifier.height(24.dp))
// PRODUCTO
Text("1. Seleccionar producto", color = Color.White)
Spacer(modifier = Modifier.height(8.dp))
Box {
OutlinedButton(
onClick = { menuProducto = true },
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp)
) {
Text(
productoSeleccionado?.nombre ?: "Elegir producto",
modifier = Modifier.weight(1f)
)
Text("▼")
}
DropdownMenu(
expanded = menuProducto,
onDismissRequest = { menuProducto = false }
) {
productos.filter { it.activo }.forEach { producto ->
DropdownMenuItem(
text = { Text(producto.nombre) },
onClick = {
productoSeleccionado = producto
aromaSeleccionado = null
presentacionSeleccionada = null
menuProducto = false
}
)
}
}
}
Spacer(modifier = Modifier.height(18.dp))
// AROMA
Text("2. Seleccionar aroma", color = Color.White)
Spacer(modifier = Modifier.height(8.dp))
Box {
OutlinedButton(
onClick = { menuAroma = true },
enabled = productoSeleccionado != null,
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp)
) {
Text(
aromaSeleccionado?.nombre ?: "Elegir aroma",
modifier = Modifier.weight(1f)
)
Text("▼")
}
DropdownMenu(
expanded = menuAroma,
onDismissRequest = { menuAroma = false }
) {
aromasDisponibles.forEach { aroma ->
DropdownMenuItem(
text = { Text(aroma.nombre) },
onClick = {
aromaSeleccionado = aroma
presentacionSeleccionada = null
menuAroma = false
}
)
}
}
}
Spacer(modifier = Modifier.height(18.dp))
// PRESENTACION
Text("3. Seleccionar presentación", color = Color.White)
Spacer(modifier = Modifier.height(8.dp))
Box {
OutlinedButton(
onClick = { menuPresentacion = true },
enabled = aromaSeleccionado != null,
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp)
) {
Text(
presentacionSeleccionada?.nombre
?: "Elegir presentación",
modifier = Modifier.weight(1f)
)
Text("▼")
}
DropdownMenu(
expanded = menuPresentacion,
onDismissRequest = { menuPresentacion = false }
) {
presentacionesDisponibles.forEach { presentacion ->
DropdownMenuItem(
text = {
Text(
"${presentacion.nombre} • ${presentacion.contenidoMl} ml"
)
},
onClick = {
presentacionSeleccionada = presentacion
menuPresentacion = false
}
)
}
}
}
Spacer(modifier = Modifier.height(24.dp))
// CANTIDAD FABRICADA
Text(
"4. Cantidad fabricada",
color = Color.White,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = cantidadProducida,
onValueChange = {
cantidadProducida = it.filter { c -> c.isDigit() }
},
modifier = Modifier.fillMaxWidth(),
placeholder = { Text("Ejemplo: 100") },
label = { Text("Unidades fabricadas") },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number
),
singleLine = true
)
Spacer(modifier = Modifier.height(24.dp))
// DISTRIBUCION
Text(
"5. Distribución por sede",
color = Color.White,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(8.dp))
Card(
colors = CardDefaults.cardColors(
containerColor = CardBlue
),
shape = RoundedCornerShape(16.dp)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(
"Barrancas",
color = Color.White,
fontWeight = FontWeight.Medium
)
Spacer(modifier = Modifier.height(6.dp))
OutlinedTextField(
value = cantidadBarrancas,
onValueChange = {
cantidadBarrancas = it.filter { c -> c.isDigit() }
},
modifier = Modifier.fillMaxWidth(),
placeholder = { Text("Unidades para Barrancas") },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number
),
singleLine = true
)
Spacer(modifier = Modifier.height(18.dp))
Text(
"Valledupar",
color = Color.White,
fontWeight = FontWeight.Medium
)
Spacer(modifier = Modifier.height(6.dp))
OutlinedTextField(
value = cantidadValledupar,
onValueChange = {
cantidadValledupar = it.filter { c -> c.isDigit() }
},
modifier = Modifier.fillMaxWidth(),
placeholder = { Text("Unidades para Valledupar") },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number
),
singleLine = true
)
}
}
Spacer(modifier = Modifier.height(18.dp))
// RESUMEN DE DISTRIBUCION
Text(
"Total distribuido: $totalDistribuido unidades",
color = SoftBlue,
fontWeight = FontWeight.Medium
)
Spacer(modifier = Modifier.height(10.dp))
if (mensajeValidacion != null) {
Text(
"⚠ $mensajeValidacion",
color = Color(0xFFFF7777),
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Medium
)
}
if (distribucionCorrecta) {
Text(
"✓ Distribución correcta. Todas las unidades están asignadas.",
color = Color(0xFF65D69A),
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Medium
)
}
Spacer(modifier = Modifier.height(20.dp))
// GUARDAR PRODUCCION
Button(
onClick = {
if (distribucionCorrecta) {
onGuardar(
presentacionSeleccionada!!,
producidas!!,
barrancas!!,
valledupar!!
)
}
},
enabled = distribucionCorrecta,
modifier = Modifier
.fillMaxWidth()
.height(54.dp),
shape = RoundedCornerShape(14.dp),
colors = ButtonDefaults.buttonColors(
containerColor = AccentBlue,
disabledContainerColor = Color(0xFF34445D)
)
) {
Text(
"Guardar producción",
fontWeight = FontWeight.Bold
)
}
Spacer(modifier = Modifier.height(30.dp))
}
}
package com.negocio.desinfectantes
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
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 androidx.compose.ui.unit.sp
import com.negocio.desinfectantes.ui.theme.DesinfectantesAppTheme
import com.negocio.desinfectantes.ui.productos.ProductosScreen
import com.negocio.desinfectantes.ui.productos.AgregarProductoScreen
import com.negocio.desinfectantes.ui.productos.AromasScreen
import com.negocio.desinfectantes.ui.inactivos.RegistrosInactivosScreen
import androidx.compose.ui.platform.LocalContext
import com.negocio.desinfectantes.data.DatabaseProvider
import com.negocio.desinfectantes.data.ProductoEntity
import com.negocio.desinfectantes.data.AromaEntity
import com.negocio.desinfectantes.data.PresentacionEntity
import kotlinx.coroutines.launch
import java.util.UUID
import androidx.compose.runtime.collectAsState
import com.negocio.desinfectantes.ui.productos.AgregarAromaScreen
import com.negocio.desinfectantes.ui.productos.PresentacionesScreen
import com.negocio.desinfectantes.ui.productos.AgregarPresentacionScreen
import com.negocio.desinfectantes.ui.productos.EditarPresentacionScreen
import com.negocio.desinfectantes.ui.produccion.ProduccionScreen
import com.negocio.desinfectantes.ui.inventario.InventarioScreen
import com.negocio.desinfectantes.data.ProduccionEntity
import com.negocio.desinfectantes.ui.clientes.ClienteScreen
import com.negocio.desinfectantes.data.ClienteEntity
import com.negocio.desinfectantes.data.DistribucionProduccionEntity
import com.negocio.desinfectantes.data.InventarioEntity
import com.negocio.desinfectantes.data.MovimientoInventarioEntity
import com.negocio.desinfectantes.ui.ventas.VentaScreen
import com.negocio.desinfectantes.ui.ventas.HistorialVentasScreen
import com.negocio.desinfectantes.ui.reportes.ReportesScreen
import java.util.Calendar
import java.util.Locale
import java.text.NumberFormat
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.res.painterResource
import androidx.compose.foundation.Image
import androidx.compose.ui.graphics.ColorFilter
private val Navy = Color(0xFF101D35)
private val CardBlue = Color(0xFF1B2D4A)
private val AccentBlue = Color(0xFF2585F5)
private val SoftBlue = Color(0xFFA9BEDB)
private val White = Color(0xFFFFFFFF)
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
DesinfectantesAppTheme {
PrestigeCleanApp()
}
}
}
}
@Composable
fun PrestigeCleanApp() {
var selectedTab by remember { mutableStateOf("Inicio") }
var ventaInicialId by remember {
mutableStateOf<String?>(null)
}
var mostrarFormularioProducto by remember { mutableStateOf(false) }
var mostrarRegistrosInactivos by remember {
mutableStateOf(false)
}
var productoSeleccionado by remember {
mutableStateOf<ProductoEntity?>(null)
}
var mostrarFormularioAroma by remember {
mutableStateOf(false)
}
var aromaSeleccionado by remember {
mutableStateOf<AromaEntity?>(null)
}
var mostrarFormularioPresentacion by remember {
mutableStateOf(false)
}
var mensajeAroma by remember {
mutableStateOf<String?>(null)
}
var presentacionSeleccionada by remember {
mutableStateOf<PresentacionEntity?>(null)
}
val context = LocalContext.current
val scope = rememberCoroutineScope()
val productoDao = remember {
DatabaseProvider.obtenerBaseDatos(context).productoDao()
}
val productos by productoDao.obtenerProductos()
.collectAsState(initial = emptyList())
val productosInactivos by productoDao.obtenerProductosInactivos()
.collectAsState(initial = emptyList())
val aromasInactivos by productoDao.obtenerTodosAromasInactivos()
.collectAsState(initial = emptyList())
val presentacionesInactivas by productoDao.obtenerTodasPresentacionesInactivas()
.collectAsState(initial = emptyList())
val todosProductos by productoDao.obtenerTodosProductos()
.collectAsState(initial = emptyList())
val todosAromas by productoDao.obtenerTodosAromas()
.collectAsState(initial = emptyList())
val aromasFlow = remember(productoSeleccionado?.id) {
productoSeleccionado?.let {
productoDao.obtenerAromas(it.id)
} ?: kotlinx.coroutines.flow.flowOf(emptyList())
}
val aromas by aromasFlow.collectAsState(initial = emptyList())
val presentacionesFlow = remember(aromaSeleccionado?.id) {
aromaSeleccionado?.let {
productoDao.obtenerPresentaciones(it.id)
} ?: kotlinx.coroutines.flow.flowOf(emptyList())
}
val presentaciones by presentacionesFlow.collectAsState(initial = emptyList())
val todasPresentacionesActivas by productoDao
.obtenerTodasPresentacionesActivas()
.collectAsState(initial = emptyList())
val inventarios by productoDao
.obtenerTodoInventario()
.collectAsState(initial = emptyList())
val clientes by productoDao.obtenerClientes()
.collectAsState(initial = emptyList())
val ventas by productoDao.obtenerVentas()
.collectAsState(initial = emptyList())
val todosLosPagos by productoDao.obtenerTodosLosPagos()
.collectAsState(initial = emptyList())
// FECHAS DEL DIA ACTUAL
var inicioDia by remember {
mutableStateOf(
Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
)
}
val finDia = remember(inicioDia) {
Calendar.getInstance().apply {
timeInMillis = inicioDia
add(Calendar.DAY_OF_MONTH, 1)
}.timeInMillis
}
// ACTUALIZAR AUTOMATICAMENTE AL LLEGAR LA MEDIANOCHE
LaunchedEffect(Unit) {
while (true) {
val ahora = Calendar.getInstance()
val proximaMedianoche = (ahora.clone() as Calendar).apply {
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
add(Calendar.DAY_OF_MONTH, 1)
}.timeInMillis
val tiempoEspera = proximaMedianoche - System.currentTimeMillis()
kotlinx.coroutines.delay(tiempoEspera.coerceAtLeast(1L))
inicioDia = Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
}
}
// ESTADISTICAS DE ROOM
val ventasDelDia by productoDao
.obtenerVentasDelDia(inicioDia, finDia)
.collectAsState(initial = 0.0)
val dineroRecibidoHoy by productoDao
.obtenerDineroRecibidoDelDia(inicioDia, finDia)
.collectAsState(initial = 0.0)
val totalPorCobrar by productoDao
.obtenerTotalPorCobrar()
.collectAsState(initial = 0.0)
val inventarioDisponible by productoDao
.obtenerInventarioDisponible()
.collectAsState(initial = 0)
// FORMATO DE PESOS COLOMBIANOS
val formatoPesos = remember {
NumberFormat.getCurrencyInstance(Locale("es", "CO")).apply {
maximumFractionDigits = 0
minimumFractionDigits = 0
}
}
Scaffold(
containerColor = Navy,
bottomBar = {
NavigationBar(
containerColor = CardBlue,
contentColor = White
) {
NavigationBarItem(
selected = selectedTab == "Inicio",
onClick = { selectedTab = "Inicio" },
icon = { Text("⌂", fontSize = 22.sp) },
label = { Text("Inicio") },
colors = navColors()
)
NavigationBarItem(
selected = selectedTab == "Ventas",
onClick = { selectedTab = "Ventas" },
icon = { Text("$", fontSize = 22.sp) },
label = { Text("Ventas") },
colors = navColors()
)
NavigationBarItem(
selected = selectedTab == "Productos",
onClick = { selectedTab = "Productos" },
icon = {
Icon(
painter = painterResource(id = R.drawable.ic_bottle),
contentDescription = "Productos",
tint = navColors().unselectedIconColor
)
},
label = { Text("Productos") },
colors = navColors()
)
NavigationBarItem(
selected = selectedTab == "Clientes",
onClick = { selectedTab = "Clientes" },
icon = { Text("♙", fontSize = 22.sp) },
label = { Text("Clientes") },
colors = navColors()
)
NavigationBarItem(
selected = selectedTab == "Reportes",
onClick = { selectedTab = "Reportes" },
icon = { Text("▤", fontSize = 22.sp) },
label = { Text("Reportes") },
colors = navColors()
)
}
}
) { innerPadding ->
when (selectedTab) {
"Inicio" -> DashboardScreen(
modifier = Modifier.padding(innerPadding),
onNavigate = { selectedTab = it },
ventasHoy = formatoPesos.format(ventasDelDia),
dineroRecibido = formatoPesos.format(dineroRecibidoHoy),
porCobrar = formatoPesos.format(totalPorCobrar),
inventario = inventarioDisponible.toString()
)
"Ventas" -> VentaScreen(
modifier = Modifier.padding(innerPadding),
clientes = clientes,
productos = productos,
aromas = todosAromas.filter { it.activo },
presentaciones = todasPresentacionesActivas,
productoDao = productoDao,
onVolver = { selectedTab = "Inicio" }
)
"Produccion" -> ProduccionScreen(
modifier = Modifier.padding(innerPadding),
productos = productos,
aromas = todosAromas.filter { it.activo },
presentaciones = todasPresentacionesActivas,
onVolver = {
selectedTab = "Inicio"
},
onGuardar = { presentacion, producidas, barrancas, valledupar ->
scope.launch {
val fecha = System.currentTimeMillis()
val produccionId = UUID.randomUUID().toString()
// Crear el registro del lote fabricado.
val nuevaProduccion = ProduccionEntity(
id = produccionId,
presentacionId = presentacion.id,
cantidadProducida = producidas,
fechaCreacion = fecha
)
val distribuciones = mutableListOf<DistribucionProduccionEntity>()
val inventarios = mutableListOf<InventarioEntity>()
val movimientos = mutableListOf<MovimientoInventarioEntity>()
// Preparar la distribución de las dos sedes.
val sedes = listOf(
"Barrancas" to barrancas,
"Valledupar" to valledupar
)
sedes.forEach { (sede, cantidad) ->
if (cantidad > 0) {
// Registrar la cantidad enviada a esta sede.
distribuciones.add(
DistribucionProduccionEntity(
id = UUID.randomUUID().toString(),
produccionId = produccionId,
sede = sede,
cantidad = cantidad,
fechaCreacion = fecha
)
)
// Consultar si ya existe inventario para esa sede.
val inventarioActual = productoDao.buscarInventario(
presentacionId = presentacion.id,
sede = sede
)
// Sumar las nuevas unidades a las existentes.
val inventarioActualizado = if (inventarioActual != null) {
inventarioActual.copy(
cantidadDisponible =
inventarioActual.cantidadDisponible + cantidad,
fechaActualizacion = fecha
)
} else {
InventarioEntity(
id = UUID.randomUUID().toString(),
presentacionId = presentacion.id,
sede = sede,
cantidadDisponible = cantidad,
cantidadReservada = 0,
fechaActualizacion = fecha
)
}
inventarios.add(inventarioActualizado)
// Registrar el movimiento de entrada.
movimientos.add(
MovimientoInventarioEntity(
id = UUID.randomUUID().toString(),
presentacionId = presentacion.id,
sede = sede,
tipoMovimiento = "PRODUCCION",
cantidad = cantidad,
referenciaId = produccionId,
observacion = "Entrada por producción",
fechaCreacion = fecha
)
)
}
}
// Guardar todo en una sola transacción.
productoDao.registrarProduccionCompleta(
produccion = nuevaProduccion,
distribuciones = distribuciones,
inventarios = inventarios,
movimientos = movimientos
)
selectedTab = "Inicio"
}
}
)
"Productos" -> {
if (mostrarRegistrosInactivos) {
RegistrosInactivosScreen(
productos = productosInactivos,
aromas = aromasInactivos,
presentaciones = presentacionesInactivas,
todosProductos = todosProductos,
todosAromas = todosAromas,
onVolver = {
mostrarRegistrosInactivos = false
},
onReactivarProducto = { producto ->
scope.launch {
productoDao.reactivarProducto(producto.id)
}
},
onReactivarAroma = { aroma ->
scope.launch {
productoDao.reactivarAroma(aroma.id)
}
},
onReactivarPresentacion = { presentacion ->
scope.launch {
productoDao.reactivarPresentacion(presentacion.id)
}
}
)
} else if (mostrarFormularioProducto) {
AgregarProductoScreen(
onVolver = {
mostrarFormularioProducto = false
},
onGuardar = { nombre, tipo, aroma ->
scope.launch {
val productoId = UUID.randomUUID().toString()
val producto = ProductoEntity(
id = productoId,
nombre = nombre,
tipo = tipo
)
val nuevoAroma = AromaEntity(
id = UUID.randomUUID().toString(),
productoId = productoId,
nombre = aroma
)
productoDao.guardarProducto(producto)
productoDao.guardarAroma(nuevoAroma)
mostrarFormularioProducto = false
}
}
)
} else if (productoSeleccionado != null) {
if (mostrarFormularioAroma) {
AgregarAromaScreen(
nombreProducto = productoSeleccionado!!.nombre,
onVolver = {
mostrarFormularioAroma = false
},
onGuardar = { nombreAroma ->
scope.launch {
val nuevoAroma = AromaEntity(
id = UUID.randomUUID().toString(),
productoId = productoSeleccionado!!.id,
nombre = nombreAroma
)
productoDao.guardarAroma(nuevoAroma)
mostrarFormularioAroma = false
}
}
)
} else if (aromaSeleccionado != null) {
if (presentacionSeleccionada != null) {
EditarPresentacionScreen(
nombreProducto = productoSeleccionado!!.nombre,
nombreAroma = aromaSeleccionado!!.nombre,
presentacion = presentacionSeleccionada!!,
onVolver = {
presentacionSeleccionada = null
},
onGuardar = { nombre, contenido, precio ->
scope.launch {
val presentacionActualizada = presentacionSeleccionada!!.copy(
nombre = nombre,
contenidoMl = contenido,
precio = precio
)
productoDao.guardarPresentacion(presentacionActualizada)
presentacionSeleccionada = null
}
},
onEliminar = {
scope.launch {
val idPresentacion = presentacionSeleccionada!!.id
val existencias =
productoDao.contarExistenciasPresentacion(idPresentacion)
if (existencias > 0) {
mensajeAroma =
"No se puede eliminar esta presentación porque todavía tiene $existencias unidades en inventario."
} else {
productoDao.eliminarPresentacion(idPresentacion)
}
presentacionSeleccionada = null
}
}
)
} else if (mostrarFormularioPresentacion) {
AgregarPresentacionScreen(
nombreProducto = productoSeleccionado!!.nombre,
nombreAroma = aromaSeleccionado!!.nombre,
onVolver = {
mostrarFormularioPresentacion = false
},
onGuardar = { nombre, contenido, precio ->
scope.launch {
val nuevaPresentacion = PresentacionEntity(
id = UUID.randomUUID().toString(),
aromaId = aromaSeleccionado!!.id,
nombre = nombre,
contenidoMl = contenido,
precio = precio
)
productoDao.guardarPresentacion(nuevaPresentacion)
mostrarFormularioPresentacion = false
}
}
)
} else {
PresentacionesScreen(
nombreProducto = productoSeleccionado!!.nombre,
nombreAroma = aromaSeleccionado!!.nombre,
presentaciones = presentaciones,
onVolver = {
aromaSeleccionado = null
},
onAgregarPresentacion = {
mostrarFormularioPresentacion = true
},
onSeleccionarPresentacion = { presentacion ->
presentacionSeleccionada = presentacion
}
)
}
} else {
AromasScreen(
nombreProducto = productoSeleccionado!!.nombre,
aromas = aromas,
onVolver = {
productoSeleccionado = null
},
onAgregarAroma = {
mostrarFormularioAroma = true
},
onSeleccionarAroma = { aroma ->
aromaSeleccionado = aroma
},
onEliminarAroma = { aroma ->
scope.launch {
val cantidad = productoDao.contarPresentacionesDeAroma(aroma.id)
if (cantidad == 0) {
productoDao.eliminarAroma(aroma.id)
} else {
mensajeAroma =
"No se puede eliminar este aroma porque tiene $cantidad presentación(es) registradas."
}
}
},
onEditarAroma = { aroma, nuevoNombre ->
scope.launch {
productoDao.actualizarAroma(
aromaId = aroma.id,
nuevoNombre = nuevoNombre
)
}
}
)
}
} else {
ProductosScreen(
modifier = Modifier.padding(innerPadding),
productos = productos,
onAgregarProducto = {
mostrarFormularioProducto = true
},
onVerInactivos = {
mostrarRegistrosInactivos = true
},
onSeleccionarProducto = { producto ->
productoSeleccionado = producto
},
onDesactivarProducto = { producto ->
scope.launch {
val cantidadAromas =
productoDao.contarAromasDeProducto(producto.id)
if (cantidadAromas > 0) {
mensajeAroma =
"No se puede desactivar este producto porque tiene $cantidadAromas aroma(s) registrados."
} else {
productoDao.desactivarProducto(producto.id)
}
}
},
onEditarProducto = { producto, nuevoNombre, nuevoTipo ->
scope.launch {
productoDao.actualizarProducto(
productoId = producto.id,
nombre = nuevoNombre,
tipo = nuevoTipo
)
}
}
)
}
}
"Inventario" -> InventarioScreen(
modifier = Modifier.padding(innerPadding),
productos = productos,
aromas = todosAromas,
presentaciones = todasPresentacionesActivas,
inventarios = inventarios,
onVolver = {
selectedTab = "Inicio"
}
)
"Clientes" -> ClienteScreen(
modifier = Modifier.padding(innerPadding),
clientes = clientes,
onGuardar = { cliente ->
scope.launch {
productoDao.guardarCliente(cliente)
}
},
onDesactivar = { clienteId ->
scope.launch {
productoDao.desactivarCliente(clienteId)
}
},
onVolver = {
selectedTab = "Inicio"
}
)
"Historial" -> HistorialVentasScreen(
modifier = Modifier.padding(innerPadding),
ventas = ventas,
clientes = clientes,
productos = todosProductos,
aromas = todosAromas,
presentaciones = todasPresentacionesActivas + presentacionesInactivas,
productoDao = productoDao,
onVolver = {
ventaInicialId = null
selectedTab = "Inicio"
},
ventaInicialId = ventaInicialId
)
"Reportes" -> ReportesScreen(
modifier = Modifier.padding(innerPadding),
ventas = ventas,
clientes = clientes,
pagos = todosLosPagos,
onAbrirVenta = { ventaId ->
ventaInicialId = ventaId
selectedTab = "Historial"
}
)
}
}
if (mensajeAroma != null) {
AlertDialog(
onDismissRequest = {
mensajeAroma = null
},
title = {
Text("No se puede eliminar")
},
text = {
Text(mensajeAroma!!)
},
confirmButton = {
TextButton(
onClick = {
mensajeAroma = null
}
) {
Text("Entendido")
}
}
)
}
}
@Composable
fun navColors() = NavigationBarItemDefaults.colors(
selectedIconColor = AccentBlue,
selectedTextColor = AccentBlue,
indicatorColor = Color(0xFF263F62),
unselectedIconColor = SoftBlue,
unselectedTextColor = SoftBlue
)
@Composable
fun DashboardScreen(
modifier: Modifier = Modifier,
onNavigate: (String) -> Unit,
ventasHoy: String,
dineroRecibido: String,
porCobrar: String,
inventario: String
) {
Column(
modifier = modifier
.fillMaxSize()
.background(Navy)
.verticalScroll(rememberScrollState())
.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(20.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
Box(
modifier = Modifier
.size(48.dp)
.background(AccentBlue, RoundedCornerShape(14.dp)),
contentAlignment = Alignment.Center
) {
Text(
"PC",
color = White,
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
}
Column(modifier = Modifier.weight(1f)) {
Text(
"PRESTIGE CLEAN",
color = White,
fontSize = 17.sp,
fontWeight = FontWeight.Bold
)
Text(
"Panel de administración",
color = SoftBlue,
fontSize = 12.sp
)
}
Text("●", color = Color(0xFF4ADE80), fontSize = 13.sp)
}
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(
"¡Bienvenido!",
color = White,
fontSize = 25.sp,
fontWeight = FontWeight.Bold
)
Text(
"Este es el resumen de tu negocio.",
color = SoftBlue,
fontSize = 14.sp
)
}
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
Text(
"Resumen del día",
color = White,
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
MetricCard(
"Ventas de hoy",
ventasHoy,
"Operaciones registradas",
Modifier.weight(1f)
)
MetricCard(
"Dinero recibido",
dineroRecibido,
"Pagos recibidos",
Modifier.weight(1f)
)
}
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
MetricCard(
"Por cobrar",
porCobrar,
"Saldos pendientes",
Modifier.weight(1f)
)
MetricCard(
"Inventario",
inventario,
"Unidades disponibles",
Modifier.weight(1f)
)
}
}
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
Text(
"Accesos rápidos",
color = White,
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
ActionCard(
"+",
"Nueva venta",
Modifier.weight(1f)
) { onNavigate("Ventas") }
ActionCard(
symbol = "",
title = "Productos",
modifier = Modifier.weight(1f),
iconRes = R.drawable.ic_bottle
) {
onNavigate("Productos")
}
}
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
ActionCard(
"♙",
"Clientes",
Modifier.weight(1f)
) { onNavigate("Clientes") }
ActionCard(
"◷",
"Historial",
Modifier.weight(1f)
) { onNavigate("Historial") }
}
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
ActionCard(
"⚙",
"Producción",
Modifier.weight(1f)
) { onNavigate("Produccion") }
ActionCard(
"▤",
"Inventario",
Modifier.weight(1f)
) { onNavigate("Inventario") }
}
}
Text(
"Prestige Clean • Gestión inteligente de tu negocio",
color = SoftBlue,
fontSize = 11.sp,
modifier = Modifier
.fillMaxWidth()
.padding(top = 4.dp),
)
}
}
@Composable
fun MetricCard(
title: String,
value: String,
description: String,
modifier: Modifier = Modifier
) {
Card(
modifier = modifier,
shape = RoundedCornerShape(18.dp),
colors = CardDefaults.cardColors(
containerColor = CardBlue
)
) {
Column(
modifier = Modifier.padding(14.dp),
verticalArrangement = Arrangement.spacedBy(7.dp)
) {
Text(
title,
color = SoftBlue,
fontSize = 12.sp
)
Text(
value,
color = White,
fontSize = 23.sp,
fontWeight = FontWeight.Bold
)
Text(
description,
color = SoftBlue,
fontSize = 10.sp
)
}
}
}
@Composable
fun ActionCard(
symbol: String,
title: String,
modifier: Modifier = Modifier,
iconRes: Int? = null,
onClick: () -> Unit
) {
Card(
modifier = modifier.clickable { onClick() },
shape = RoundedCornerShape(18.dp),
colors = CardDefaults.cardColors(
containerColor = CardBlue
)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
if (iconRes != null) {
Image(
painter = painterResource(id = iconRes),
contentDescription = title,
modifier = Modifier.size(27.dp),
colorFilter = ColorFilter.tint(AccentBlue)
)
} else {
Text(
symbol,
color = AccentBlue,
fontSize = 27.sp,
fontWeight = FontWeight.Bold
)
}
Text(
title,
color = White,
fontSize = 14.sp,
fontWeight = FontWeight.SemiBold
)
}
}
}
@Composable
fun SectionScreen(
title: String,
description: String,
symbol: String,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxSize()
.background(Navy)
.padding(24.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
title,
color = White,
fontSize = 26.sp,
fontWeight = FontWeight.Bold
)
Box(
modifier = Modifier
.size(70.dp)
.background(CardBlue, RoundedCornerShape(20.dp)),
contentAlignment = Alignment.Center
) {
Text(
symbol,
color = AccentBlue,
fontSize = 32.sp
)
}
Text(
description,
color = SoftBlue,
fontSize = 15.sp
)
Text(
"Estamos preparando esta sección.",
color = White,
fontSize = 13.sp
)
}
}
51,633 chars · codepastes.com