untitled

Pasted Nov 10, 2026, 6:34 AM1 file616 lines

Scan to open on your phone

Point a camera at the QR code - it opens this exact paste, no app needed.

https://codepastes.com/zzgs8ht28z6aqm10
untitled

package com.negocio.desinfectantes.ui.produccion

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.text.KeyboardOptions
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.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import com.negocio.desinfectantes.data.*
import java.util.UUID

private val Navy = Color(0xFF101D35)
private val CardBlue = Color(0xFF1B2D4A)
private val AccentBlue = Color(0xFF2585F5)
private val SoftBlue = Color(0xFFA9BEDB)

private data class DetalleLoteFormulario(
    val presentacion: PresentacionEntity,
    val cantidad: String = "",
    val barrancas: String = "0",
    val valledupar: String = "0"
)

private data class GastoLoteFormulario(
    val concepto: String = "",
    val monto: String = "",
    val observacion: String = ""
)

@Composable
fun ProduccionScreen(
    modifier: Modifier = Modifier,
    productos: List<ProductoEntity>,
    aromas: List<AromaEntity>,
    presentaciones: List<PresentacionEntity>,
    onVolver: () -> Unit,
    onGuardar: (
        LoteProduccionEntity,
        List<LoteDetalleEntity>,
        List<LoteGastoEntity>,
        List<LoteInventarioEntity>
    ) -> Unit
) {
    var productoSeleccionado by remember { mutableStateOf<ProductoEntity?>(null) }
    var aromaSeleccionado by remember { mutableStateOf<AromaEntity?>(null) }

    var menuProducto by remember { mutableStateOf(false) }
    var menuAroma by remember { mutableStateOf(false) }
    var menuPresentacion by remember { mutableStateOf(false) }

    val detalles = remember { mutableStateListOf<DetalleLoteFormulario>() }
    val gastos = remember { mutableStateListOf<GastoLoteFormulario>() }

    val aromasDisponibles = aromas.filter {
        it.productoId == productoSeleccionado?.id && it.activo
    }

    val presentacionesDisponibles = presentaciones.filter {
        it.aromaId == aromaSeleccionado?.id && it.activo &&
                detalles.none { detalle -> detalle.presentacion.id == it.id }
    }

    val detallesCorrectos = detalles.isNotEmpty() && detalles.all { detalle ->
        val cantidad = detalle.cantidad.toIntOrNull()
        val barrancas = detalle.barrancas.toIntOrNull()
        val valledupar = detalle.valledupar.toIntOrNull()

        cantidad != null && cantidad > 0 &&
                barrancas != null && valledupar != null &&
                barrancas >= 0 && valledupar >= 0 &&
                barrancas + valledupar == cantidad
    }

    val gastosCorrectos = gastos.all { gasto ->
        val vacio = gasto.concepto.isBlank() && gasto.monto.isBlank()
        val monto = gasto.monto.replace(",", ".").toDoubleOrNull()

        vacio || (
                gasto.concepto.isNotBlank() &&
                        monto != null && monto > 0
                )
    }

    val formularioCorrecto =
        productoSeleccionado != null &&
                aromaSeleccionado != null &&
                detallesCorrectos &&
                gastosCorrectos

    Column(
        modifier = modifier
            .fillMaxSize()
            .background(Navy)
            .verticalScroll(rememberScrollState())
            .padding(20.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        TextButton(onClick = onVolver) {
            Text("← Volver", color = SoftBlue)
        }

        Text(
            "Nuevo lote de producción",
            color = Color.White,
            style = MaterialTheme.typography.headlineMedium,
            fontWeight = FontWeight.Bold
        )

        Text(
            "Selecciona los registros existentes y añade las cantidades fabricadas.",
            color = SoftBlue
        )

        // PRODUCTO
        Text("1. Producto", color = Color.White, fontWeight = FontWeight.Bold)

        Box {
            OutlinedButton(
                onClick = { menuProducto = true },
                modifier = Modifier.fillMaxWidth()
            ) {
                Text(
                    productoSeleccionado?.nombre ?: "Seleccionar 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
                            detalles.clear()
                            menuProducto = false
                        }
                    )
                }
            }
        }

        // AROMA
        Text("2. Aroma", color = Color.White, fontWeight = FontWeight.Bold)

        Box {
            OutlinedButton(
                onClick = { menuAroma = true },
                enabled = productoSeleccionado != null,
                modifier = Modifier.fillMaxWidth()
            ) {
                Text(
                    aromaSeleccionado?.nombre ?: "Seleccionar aroma",
                    modifier = Modifier.weight(1f)
                )
                Text("▼")
            }

            DropdownMenu(
                expanded = menuAroma,
                onDismissRequest = { menuAroma = false }
            ) {
                aromasDisponibles.forEach { aroma ->
                    DropdownMenuItem(
                        text = { Text(aroma.nombre) },
                        onClick = {
                            aromaSeleccionado = aroma
                            detalles.clear()
                            menuAroma = false
                        }
                    )
                }
            }
        }

        // PRESENTACIONES
        Text(
            "3. Presentaciones del lote",
            color = Color.White,
            fontWeight = FontWeight.Bold
        )

        Text(
            "Puedes agregar varias presentaciones al mismo lote.",
            color = SoftBlue
        )

        Box {
            OutlinedButton(
                onClick = { menuPresentacion = true },
                enabled = aromaSeleccionado != null &&
                        presentacionesDisponibles.isNotEmpty(),
                modifier = Modifier.fillMaxWidth()
            ) {
                Text(
                    "+ Agregar 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 = {
                            detalles.add(
                                DetalleLoteFormulario(
                                    presentacion = presentacion
                                )
                            )
                            menuPresentacion = false
                        }
                    )
                }
            }
        }

        if (aromaSeleccionado != null &&
            presentacionesDisponibles.isEmpty() &&
            detalles.isEmpty()
        ) {
            Text(
                "No hay presentaciones disponibles para este aroma.",
                color = SoftBlue
            )
        }

        detalles.forEachIndexed { index, detalle ->
            Card(
                modifier = Modifier.fillMaxWidth(),
                colors = CardDefaults.cardColors(
                    containerColor = CardBlue
                ),
                shape = RoundedCornerShape(16.dp)
            ) {
                Column(
                    modifier = Modifier.padding(16.dp),
                    verticalArrangement = Arrangement.spacedBy(10.dp)
                ) {
                    Row(
                        verticalAlignment = Alignment.CenterVertically
                    ) {
                        Column(modifier = Modifier.weight(1f)) {
                            Text(
                                detalle.presentacion.nombre,
                                color = Color.White,
                                fontWeight = FontWeight.Bold
                            )
                            Text(
                                "${detalle.presentacion.contenidoMl} ml",
                                color = SoftBlue
                            )
                        }

                        TextButton(
                            onClick = { detalles.removeAt(index) }
                        ) {
                            Text("Quitar", color = Color(0xFFFF8888))
                        }
                    }

                    OutlinedTextField(
                        value = detalle.cantidad,
                        onValueChange = {
                            detalles[index] = detalle.copy(
                                cantidad = it.filter { c -> c.isDigit() }
                            )
                        },
                        modifier = Modifier.fillMaxWidth(),
                        label = { Text("Unidades fabricadas") },
                        keyboardOptions = KeyboardOptions(
                            keyboardType = KeyboardType.Number
                        ),
                        singleLine = true
                    )

                    Text("Distribución por sede", color = SoftBlue)

                    OutlinedTextField(
                        value = detalle.barrancas,
                        onValueChange = {
                            detalles[index] = detalle.copy(
                                barrancas = it.filter { c -> c.isDigit() }
                            )
                        },
                        modifier = Modifier.fillMaxWidth(),
                        label = { Text("Barrancas") },
                        keyboardOptions = KeyboardOptions(
                            keyboardType = KeyboardType.Number
                        ),
                        singleLine = true
                    )

                    OutlinedTextField(
                        value = detalle.valledupar,
                        onValueChange = {
                            detalles[index] = detalle.copy(
                                valledupar = it.filter { c -> c.isDigit() }
                            )
                        },
                        modifier = Modifier.fillMaxWidth(),
                        label = { Text("Valledupar") },
                        keyboardOptions = KeyboardOptions(
                            keyboardType = KeyboardType.Number
                        ),
                        singleLine = true
                    )

                    val cantidad = detalle.cantidad.toIntOrNull()
                    val barrancas = detalle.barrancas.toIntOrNull()
                    val valledupar = detalle.valledupar.toIntOrNull()

                    if (cantidad != null && barrancas != null &&
                        valledupar != null && cantidad > 0
                    ) {
                        val diferencia = cantidad - barrancas - valledupar

                        Text(
                            when {
                                diferencia == 0 ->
                                    "✓ Distribución correcta"
                                diferencia > 0 ->
                                    "Faltan $diferencia unidades por distribuir"
                                else ->
                                    "Hay ${-diferencia} unidades distribuidas de más"
                            },
                            color = if (diferencia == 0)
                                Color(0xFF65D69A)
                            else
                                Color(0xFFFF8888)
                        )
                    }
                }
            }
        }

        // GASTOS
        Text(
            "4. Gastos del lote",
            color = Color.White,
            fontWeight = FontWeight.Bold
        )

        Text(
            "Registra los gastos relacionados con la fabricación.",
            color = SoftBlue
        )

        gastos.forEachIndexed { index, gasto ->
            Card(
                modifier = Modifier.fillMaxWidth(),
                colors = CardDefaults.cardColors(
                    containerColor = CardBlue
                ),
                shape = RoundedCornerShape(16.dp)
            ) {
                Column(
                    modifier = Modifier.padding(16.dp),
                    verticalArrangement = Arrangement.spacedBy(10.dp)
                ) {
                    Row(verticalAlignment = Alignment.CenterVertically) {
                        Text(
                            "Gasto ${index + 1}",
                            color = Color.White,
                            fontWeight = FontWeight.Bold,
                            modifier = Modifier.weight(1f)
                        )
                        TextButton(
                            onClick = { gastos.removeAt(index) }
                        ) {
                            Text("Quitar", color = Color(0xFFFF8888))
                        }
                    }

                    OutlinedTextField(
                        value = gasto.concepto,
                        onValueChange = {
                            gastos[index] = gasto.copy(concepto = it)
                        },
                        modifier = Modifier.fillMaxWidth(),
                        label = { Text("Concepto del gasto") },
                        placeholder = { Text("Ej. Esencias, envases, etiquetas") },
                        singleLine = true
                    )

                    OutlinedTextField(
                        value = gasto.monto,
                        onValueChange = {
                            gastos[index] = gasto.copy(
                                monto = it.filter { c ->
                                    c.isDigit() || c == '.' || c == ','
                                }
                            )
                        },
                        modifier = Modifier.fillMaxWidth(),
                        label = { Text("Valor del gasto") },
                        keyboardOptions = KeyboardOptions(
                            keyboardType = KeyboardType.Decimal
                        ),
                        singleLine = true
                    )

                    OutlinedTextField(
                        value = gasto.observacion,
                        onValueChange = {
                            gastos[index] = gasto.copy(observacion = it)
                        },
                        modifier = Modifier.fillMaxWidth(),
                        label = { Text("Observación (opcional)") }
                    )
                }
            }
        }

        OutlinedButton(
            onClick = { gastos.add(GastoLoteFormulario()) },
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("+ Agregar gasto")
        }

        // RESUMEN
        val totalUnidades = detalles.sumOf {
            it.cantidad.toIntOrNull() ?: 0
        }

        val totalGastos = gastos.sumOf {
            it.monto.replace(",", ".").toDoubleOrNull() ?: 0.0
        }

        Card(
            modifier = Modifier.fillMaxWidth(),
            colors = CardDefaults.cardColors(
                containerColor = CardBlue
            )
        ) {
            Column(
                modifier = Modifier.padding(16.dp),
                verticalArrangement = Arrangement.spacedBy(6.dp)
            ) {
                Text("Resumen del lote", color = Color.White, fontWeight = FontWeight.Bold)
                Text("Presentaciones: ${detalles.size}", color = SoftBlue)
                Text("Unidades fabricadas: $totalUnidades", color = SoftBlue)
                Text(
                    "Gastos registrados: $${totalGastos.toLong()}",
                    color = SoftBlue
                )
            }
        }

        Button(
            onClick = {
                val producto = productoSeleccionado!!
                val aroma = aromaSeleccionado!!
                val fecha = System.currentTimeMillis()
                val loteId = UUID.randomUUID().toString()

                val lote = LoteProduccionEntity(
                    id = loteId,
                    productoId = producto.id,
                    aromaId = aroma.id,
                    fechaCreacion = fecha
                )

                val detallesLote = detalles.map { detalle ->
                    LoteDetalleEntity(
                        id = UUID.randomUUID().toString(),
                        loteId = loteId,
                        presentacionId = detalle.presentacion.id,
                        cantidadFabricada = detalle.cantidad.toInt()
                    )
                }

                val gastosLote = gastos
                    .filter { it.concepto.isNotBlank() }
                    .map { gasto ->
                        LoteGastoEntity(
                            id = UUID.randomUUID().toString(),
                            loteId = loteId,
                            concepto = gasto.concepto.trim(),
                            monto = gasto.monto.replace(",", ".").toDouble(),
                            observacion = gasto.observacion
                                .trim()
                                .ifBlank { null },
                            fechaCreacion = fecha
                        )
                    }

                val inventariosLote = mutableListOf<LoteInventarioEntity>()

                detalles.forEach { detalle ->
                    val cantidadBarrancas = detalle.barrancas.toInt()
                    val cantidadValledupar = detalle.valledupar.toInt()

                    if (cantidadBarrancas > 0) {
                        inventariosLote.add(
                            LoteInventarioEntity(
                                id = UUID.randomUUID().toString(),
                                loteId = loteId,
                                presentacionId = detalle.presentacion.id,
                                sede = "Barrancas",
                                cantidadDisponible = cantidadBarrancas
                            )
                        )
                    }

                    if (cantidadValledupar > 0) {
                        inventariosLote.add(
                            LoteInventarioEntity(
                                id = UUID.randomUUID().toString(),
                                loteId = loteId,
                                presentacionId = detalle.presentacion.id,
                                sede = "Valledupar",
                                cantidadDisponible = cantidadValledupar
                            )
                        )
                    }
                }

                onGuardar(
                    lote,
                    detallesLote,
                    gastosLote,
                    inventariosLote
                )
            },
            enabled = formularioCorrecto,
            modifier = Modifier
                .fillMaxWidth()
                .height(54.dp),
            shape = RoundedCornerShape(14.dp),
            colors = ButtonDefaults.buttonColors(
                containerColor = AccentBlue,
                disabledContainerColor = Color(0xFF34445D)
            )
        ) {
            Text(
                "Guardar lote de producción",
                fontWeight = FontWeight.Bold
            )
        }

        if (!formularioCorrecto) {
            Text(
                "Completa los datos y verifica que la distribución de cada presentación coincida con las unidades fabricadas.",
                color = SoftBlue
            )
        }

        Spacer(modifier = Modifier.height(24.dp))
    }
}


package com.negocio.desinfectantes.data

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "lotes_produccion")
data class LoteProduccionEntity(

    @PrimaryKey
    val id: String,

    val productoId: String,

    val aromaId: String,

    @ColumnInfo(defaultValue = "''")
    val numeroLote: String = "",

    val fechaCreacion: Long = System.currentTimeMillis()
)

package com.negocio.desinfectantes.data

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "lotes_detalle")
data class LoteDetalleEntity(

    @PrimaryKey
    val id: String,

    val loteId: String,

    val presentacionId: String,

    val cantidadFabricada: Int
)

21,493 chars · codepastes.com