"use client" import { useState, useEffect, useMemo, useRef } from "react" import { useRouter, useSearchParams } from "next/navigation" import { DashboardLayout } from "@/components/dashboard-layout" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogDescription } from "@/components/ui/dialog" import { Textarea } from "@/components/ui/textarea" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { Leaf, Plus, Trash2, Save, Printer, Calendar, MapPin, User, Sprout, ArrowLeft, ChevronDown, Check, Tractor, Pencil, GripVertical } from "lucide-react" import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { apiFetch, getErrorMessage } from "@/lib/apiutils" import { useAuth } from "@/lib/auth-context" import { useApi } from "@/lib/use-api" // Fixed import to use named export instead of default import Link from "next/link" import { toast } from "sonner" import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, DragEndEvent } from '@dnd-kit/core' import { arrayMove, SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' import { restrictToVerticalAxis, restrictToParentElement } from '@dnd-kit/modifiers' interface Productor { id: string nombre: string } interface Establecimiento { id: string nombre: string productorId: string ubicacion: string hectareas: number } interface Insumo { id: string nombreComercial: string principioActivo: string unidadMedida: string categoriaId: string categoriaNombre: string } interface ProductoReceta { id: string insumoId: string nombreProducto: string dosis: number | string unidad: string cantidadTotal: number orden: number } interface RecetaItemState { id: string loteId: string loteNombre: string loteSuperficie: number cultivoId: string cultivoNombre: string labor: string rinde?: string momentoDia: string productos: ProductoReceta[] total_recetas: number total_superficie: number total_establecimientos: number } function SortableProductRow({ producto, index, total, onReorder, children }: any) { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: producto.id }); const style = { transform: CSS.Transform.toString(transform), transition, ...(isDragging ? { position: 'relative' as const, zIndex: 99, opacity: 0.8, backgroundColor: 'var(--background)' } : {}) }; const displayOrder = producto.orden > 0 ? producto.orden : index + 1; return (
{ const val = parseInt(e.target.value); if (!isNaN(val)) { const clamped = Math.max(1, Math.min(total, val)); onReorder(producto.id, clamped); } }} className="w-9 h-7 text-center text-sm font-medium rounded-md border bg-muted focus:outline-none focus:ring-1 focus:ring-primary [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" />
{children}
); } export default function NuevaRecetaPage() { const router = useRouter() const { user } = useAuth() const useApiHook = useApi // Declare useApi hook // Use SWR hooks // Use SWR hooks const { data: prodData, mutate: mutateProductor } = useApiHook("/productores?all=true&status=activo") const rawProductores = prodData?.data || [] const productores: Productor[] = useMemo(() => rawProductores.map((p: any) => ({ id: String(p.id), nombre: p.nombre, })), [rawProductores]) const { data: estData, mutate: mutateEstablecimiento } = useApiHook("/establecimientos?all=true&status=activo") const rawEstablecimientos = estData?.data || [] const todosEstablecimientos: Establecimiento[] = useMemo(() => rawEstablecimientos.map((e: any) => ({ id: String(e.id), nombre: e.nombre, productorId: String(e.productor_id), ubicacion: e.ubicacion, hectareas: Number(e.hectareas), })), [rawEstablecimientos]) const { data: insuData, mutate: mutateInsumo } = useApiHook("/insumos?limit=1000") const rawInsumos = insuData?.data || [] const insumos: Insumo[] = useMemo(() => rawInsumos.map((i: any) => ({ id: String(i.id), nombreComercial: i.nombre_comercial, principioActivo: i.principio_activo, unidadMedida: i.unidad_medida, categoriaId: String(i.categoria_id || ""), categoriaNombre: i.categoria?.nombre || "", })), [rawInsumos]) const { data: catData } = useApiHook("/categoria-insumos") const categoriasInsumo = Array.isArray(catData) ? catData : (catData?.data || []) const { data: cultData } = useApiHook("/cultivos") const rawCultivos = cultData || [] const cultivosList = Array.isArray(rawCultivos) ? rawCultivos : (rawCultivos.data || []) const cultivos = useMemo(() => cultivosList.map((c: any) => ({ id: String(c.id), nombre: c.nombre })), [cultivosList]) const { data: prestData, mutate: mutatePrestador } = useApiHook("/prestadores?status=activo") const rawPrestadores = Array.isArray(prestData) ? prestData : (prestData?.data || []) const prestadoresList = useMemo(() => Array.isArray(rawPrestadores) ? rawPrestadores : [], [rawPrestadores]) const { data: laboresData } = useApiHook("/labores?status=activo") const defaultLabores = ["Pulverización", "Fertilización", "Siembra", "Barbecho", "Aplicación Foliar", "Cosecha"] const labores: string[] = useMemo(() => { const raw = Array.isArray(laboresData) ? laboresData : (laboresData?.data || []) if (raw && raw.length > 0) { return raw.map((l: any) => l.nombre) } return defaultLabores }, [laboresData]) const [numeroReceta, setNumeroReceta] = useState("") const [fechaActual, setFechaActual] = useState("") const [productorSeleccionado, setProductorSeleccionado] = useState("") const [establecimientoSeleccionado, setEstablecimientoSeleccionado] = useState("") const [prestadorSeleccionado, setPrestadorSeleccionado] = useState("ninguno") const [isInsumoDialogOpen, setIsInsumoDialogOpen] = useState(false) const [filtroCategoria, setFiltroCategoria] = useState>({}) const [newInsumo, setNewInsumo] = useState({ nombre_comercial: "", principio_activo: "", unidad_medida: "Litros", categoria_id: "" }) const handleSaveInsumo = async () => { const res = await apiFetch("/insumos", { method: "POST", body: JSON.stringify({ ...newInsumo, status: "activo" }) }) if (res.ok) { await mutateInsumo() setIsInsumoDialogOpen(false) setNewInsumo({ nombre_comercial: "", principio_activo: "", unidad_medida: "Litros", categoria_id: "" }) } else { toast.error(await getErrorMessage(res)) } } const [cultivo, setCultivo] = useState("") const [lotesSeleccionados, setLotesSeleccionados] = useState([]) const [superficie, setSuperficie] = useState(0) const [labor, setLabor] = useState("") const [rinde, setRinde] = useState("") const [momentoDia, setMomentoDia] = useState("") const [superficieAplicable, setSuperficieAplicable] = useState(0) const [superficieAplicableInput, setSuperficieAplicableInput] = useState("") const [laborConfirmada, setLaborConfirmada] = useState(false) const superficieEditadaManualmente = useRef(false) const [productos, setProductos] = useState([ { id: "1", insumoId: "", nombreProducto: "", dosis: 0, unidad: "L/ha", cantidadTotal: 0, orden: 1, }, ]) const [campanas, setCampanas] = useState([]) const [campanaSeleccionada, setCampanaSeleccionada] = useState("") const [campanasLoading, setCampanasLoading] = useState(false) // Fetch campaigns dependent on establishment // Fetch active campaigns globally (independent of establishment) useEffect(() => { setCampanasLoading(true) apiFetch(`/campanas`) .then((res) => res.json()) .then((data: any) => { const allCampaigns = data.data || [] setCampanas(allCampaigns) if (allCampaigns.length > 0 && !campanaSeleccionada) { const activeCampaign = allCampaigns.find((c: any) => c.estado === 'activa') if (activeCampaign) { setCampanaSeleccionada(String(activeCampaign.id)) } else { setCampanaSeleccionada(String(allCampaigns[0].id)) } } }) .catch((err) => console.error(err)) .finally(() => setCampanasLoading(false)) }, []) // Run once on mount (or when we want to refresh?) - Just on mount is fine for "Global" list const [aplicarConViento, setAplicarConViento] = useState("NO") const [vientoDireccion, setVientoDireccion] = useState([]) // Changed to array for multi-select const [vientoIntensidad, setVientoIntensidad] = useState("") const [caudal, setCaudal] = useState("") const [pastillas, setPastillas] = useState("") const [gotas, setGotas] = useState("") // Added gotas field const [deriva, setDeriva] = useState("") // Added deriva field const [observaciones, setObservaciones] = useState("") const [ingenieroResponsable, setIngenieroResponsable] = useState("") const [matricula, setMatricula] = useState("") useEffect(() => { setNumeroReceta("(Autogenerado)") const fecha = new Date().toLocaleDateString("es-AR", { day: "2-digit", month: "2-digit", year: "numeric", }) setFechaActual(fecha) }, []) const searchParams = useSearchParams() const establishmentParam = searchParams.get("establecimiento_id") const campanaParam = searchParams.get("campana_id") const cultivoParam = searchParams.get("cultivo_id") const backUrlParam = searchParams.get("back_url") const [paramsLoaded, setParamsLoaded] = useState(false) // Use Effect to handle initial params loading useEffect(() => { if (todosEstablecimientos.length > 0 && establishmentParam && !paramsLoaded) { const est = todosEstablecimientos.find(e => e.id === establishmentParam) if (est) { setProductorSeleccionado(est.productorId) // We need to wait for the producer selection effect to run potentially, // or just set filtered establishments directly? // Better to let the producer effect run, but we need to ensure establishment is selected after. // Actually, we can just set it here, but the producer effect clears it. // So we need to handle the sequence. // Let's set producer first. The existing effect [productorSeleccionado] clears establishment. // So we should utilize a state to know we are in "initializing" mode or simply set it after a tick? // Or simpler: Modify the producer effect to NOT clear if we are matching the desired establishment. // Alternative: Set everything here and ensure logic doesn't conflict. // Let's try setting producer, and rely on a separate specific effect for the params that runs when producer matches. } } }, [todosEstablecimientos, establishmentParam, paramsLoaded]) // Allow manual override via URL param (Global campaigns) useEffect(() => { if (campanaParam && campanas.length > 0) { // If param provided, override auto-selection setCampanaSeleccionada(campanaParam) } }, [campanaParam, campanas]) const establecimientosFiltrados = useMemo(() => { if (productorSeleccionado) { return todosEstablecimientos.filter((est) => est.productorId === productorSeleccionado) } return todosEstablecimientos }, [productorSeleccionado, todosEstablecimientos]) // Auto-select first producer ONLY if there's only one useEffect(() => { if (productores.length === 1 && !productorSeleccionado && !establishmentParam && !paramsLoaded) { setProductorSeleccionado(String(productores[0].id)) } }, [productores, productorSeleccionado, establishmentParam, paramsLoaded]) // Auto-select the only establishment if producer is selected and they have exactly one useEffect(() => { if (establecimientosFiltrados.length === 1 && !establecimientoSeleccionado && !establishmentParam && !paramsLoaded) { setEstablecimientoSeleccionado(String(establecimientosFiltrados[0].id)) } }, [establecimientosFiltrados, establecimientoSeleccionado, establishmentParam, paramsLoaded]) // Cross-autocomplete: when establecimiento is selected, auto-fill the producer useEffect(() => { if (establecimientoSeleccionado) { const est = todosEstablecimientos.find(e => e.id === establecimientoSeleccionado) if (est && est.productorId !== productorSeleccionado) { setProductorSeleccionado(est.productorId) } } }, [establecimientoSeleccionado, todosEstablecimientos]) // When producer changes, reset establishment only if it doesn't belong to the new producer useEffect(() => { if (establecimientoSeleccionado && productorSeleccionado) { const est = todosEstablecimientos.find(e => e.id === establecimientoSeleccionado) if (est && est.productorId !== productorSeleccionado) { setEstablecimientoSeleccionado("") setSuperficie(0) } } else if (!paramsLoaded && establishmentParam) { const est = todosEstablecimientos.find(e => e.id === establishmentParam) if (est && est.productorId === productorSeleccionado) { setEstablecimientoSeleccionado(establishmentParam) } } }, [productorSeleccionado, todosEstablecimientos]) // Separate effect to handle Campana selection after establishment is set useEffect(() => { if (establishmentParam && !paramsLoaded && establecimientoSeleccionado === establishmentParam) { let campanaReady = true if (campanaParam) { if (campanas.length > 0) { const camp = campanas.find(c => String(c.id) === campanaParam) if (camp) { setCampanaSeleccionada(campanaParam) } } else { campanaReady = false } } if (cultivoParam) { setCultivo(cultivoParam) } if (campanaReady) { setParamsLoaded(true) } } }, [establecimientoSeleccionado, campanas, establishmentParam, campanaParam, cultivoParam, paramsLoaded]) // Lotes logic const [lotes, setLotes] = useState([]) const [lotesLoading, setLotesLoading] = useState(false) useEffect(() => { if (establecimientoSeleccionado) { setLotesLoading(true) apiFetch(`/lotes?establecimiento_id=${establecimientoSeleccionado}`) .then((res) => res.json()) .then((data) => { if (Array.isArray(data)) { setLotes(data) setLotesSeleccionados(prev => prev.filter(id => data.some((l: any) => String(l.id) === id))) } else { setLotes([]) setLotesSeleccionados([]) } }) .catch((err) => console.error(err)) .finally(() => setLotesLoading(false)) setSuperficie(0) } else { setLotes([]) setLotesSeleccionados([]) setSuperficie(0) } }, [establecimientoSeleccionado]) useEffect(() => { if (lotesSeleccionados.length > 0 && lotes.length > 0) { const selectedLotesObj = lotes.filter(l => lotesSeleccionados.includes(String(l.id))) const totalSup = selectedLotesObj.reduce((acc, l) => acc + Number(l.superficie), 0) setSuperficie(totalSup) // Only auto-fill superficieAplicable if the user hasn't manually entered a value if (!superficieEditadaManualmente.current) { setSuperficieAplicable(totalSup) setSuperficieAplicableInput(totalSup === 0 ? '' : String(totalSup)) } // Use the last lote in selection order (not filtered order) to auto-select cultivo const lastSelectedId = lotesSeleccionados[lotesSeleccionados.length - 1] const lastSelectedLote = lotes.find((l: any) => String(l.id) === String(lastSelectedId)) if (lastSelectedLote?.cultivo_id) { setCultivo(String(lastSelectedLote.cultivo_id)) } } else { setSuperficie(0) if (!superficieEditadaManualmente.current) { setSuperficieAplicable(0) setSuperficieAplicableInput("") } } }, [lotesSeleccionados, lotes]) useEffect(() => { if (user) { setIngenieroResponsable((prev) => prev || user.name || "") if (user.matricula) { setMatricula((prev) => prev || user.matricula || "") } } }, [user]) const agregarProducto = () => { // Block if there's already an incomplete row (no product selected or no dose) const hayFilaVacia = productos.some(p => !p.nombreProducto || Number(p.dosis) === 0) if (hayFilaVacia) { toast.warning("Completá el producto y la dosis antes de agregar una nueva fila.") return } const nuevoProducto: ProductoReceta = { id: String(Date.now()), insumoId: "", nombreProducto: "", dosis: 0, unidad: "L/ha", cantidadTotal: 0, orden: productos.length + 1, } setProductos([...productos, nuevoProducto]) } const reorderProducts = (productId: string, newOrden: number) => { setProductos(prev => { const targetProduct = prev.find(p => p.id === productId); if (!targetProduct) return prev; const oldOrden = targetProduct.orden; if (oldOrden === newOrden) return prev; return prev.map(p => { if (p.id === productId) { return { ...p, orden: newOrden }; } if (oldOrden < newOrden) { if (p.orden > oldOrden && p.orden <= newOrden) { return { ...p, orden: p.orden - 1 }; } } else { if (p.orden >= newOrden && p.orden < oldOrden) { return { ...p, orden: p.orden + 1 }; } } return p; }).sort((a, b) => a.orden - b.orden); }); } const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 5, }, }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }) ); const handleDragEnd = (event: DragEndEvent) => { const { active, over } = event; if (over && active.id !== over.id) { setProductos((items) => { const oldIndex = items.findIndex((i) => i.id === active.id); const newIndex = items.findIndex((i) => i.id === over.id); const newItems = arrayMove(items, oldIndex, newIndex); return newItems.map((item, index) => ({ ...item, orden: index + 1 })); }); } }; const eliminarProducto = (id: string) => { setProductos(prev => { const filtered = prev.filter((p) => p.id !== id); return filtered.sort((a,b) => a.orden - b.orden).map((p, index) => ({ ...p, orden: index + 1 })); }); } const actualizarProducto = (id: string, campo: keyof ProductoReceta, valor: any) => { setProductos( productos.map((p) => { if (p.id === id) { const productoActualizado = { ...p, [campo]: valor } if (campo === "insumoId" && valor) { const insumo = insumos.find((i) => i.id === valor) if (insumo) { productoActualizado.nombreProducto = insumo.nombreComercial productoActualizado.unidad = `${insumo.unidadMedida}/ha` } } if (campo === "dosis" || (campo === "insumoId" && Number(productoActualizado.dosis) > 0)) { productoActualizado.cantidadTotal = Number((Number(productoActualizado.dosis) * Number(superficieAplicable)).toFixed(3)) } return productoActualizado } return p }), ) } useEffect(() => { setProductos( productos.map((p) => ({ ...p, cantidadTotal: Number((Number(p.dosis) * Number(superficieAplicable)).toFixed(3)), })), ) }, [superficieAplicable]) const handleGuardar = async () => { // Validaciones específicas if (!productorSeleccionado) { toast.error("Por favor seleccione un Productor.") return } if (!establecimientoSeleccionado) { toast.error("Por favor seleccione un Establecimiento.") return } if (lotesSeleccionados.length === 0) { toast.error("Por favor seleccione al menos un Lote afectado.") return } if (!labor) { toast.error("Por favor seleccione una Labor.") return } if (!cultivo) { toast.error("Por favor seleccione el Cultivo.") return } if (labor === "Cosecha" && (!rinde || Number(rinde) <= 0)) { toast.error("Para la labor de Cosecha, debe especificar un rinde mayor a 0.") return } if (labor !== "Cosecha" && productos.length === 0) { toast.error("Debe agregar al menos un producto (o seleccionar Cosecha).") return } if (!ingenieroResponsable) { toast.error("Por favor ingrese el nombre del Ingeniero Responsable.") return } if (!matricula) { toast.error("Por favor ingrese la Matrícula del Ingeniero.") return } try { // Formatear fecha para backend const [day, month, year] = fechaActual.split("/") const formattedDate = `${year}-${month}-${day}` const payload = { numero: numeroReceta, fecha: formattedDate, productor_id: productorSeleccionado, establecimiento_id: establecimientoSeleccionado, campana_id: campanaSeleccionada || null, superficie: Number(superficieAplicable), estado: "borrador", aplicar_con_viento: aplicarConViento === "SI", viento_direccion: vientoDireccion.join(", "), viento_intensidad: vientoIntensidad, caudal, pastillas, gotas, deriva, observaciones, ingeniero_responsable: ingenieroResponsable, prestador_id: prestadorSeleccionado === "ninguno" ? null : prestadorSeleccionado, matricula, items: lotesSeleccionados.map(loteId => { const loteObj = lotes.find((l: any) => String(l.id) === String(loteId)) return { lote_id: loteId, cultivo_id: loteObj?.cultivo_id ? String(loteObj.cultivo_id) : cultivo, labor: labor, rinde: rinde ? Number(rinde) : null, momento_dia: momentoDia, productos: productos.filter(p => p.nombreProducto).map(p => ({ insumo_id: p.insumoId, nombre_producto: p.nombreProducto, dosis: p.dosis, unidad: p.unidad, cantidad_total: p.cantidadTotal, orden: p.orden })) } }) } const res = await apiFetch("/recetas", { method: "POST", body: JSON.stringify(payload), }) if (res.ok) { clearDraft() toast.success("Receta guardada exitosamente!") router.push("/dashboard/recetas") } else { const msg = await getErrorMessage(res) toast.error(msg) } } catch (e) { console.error(e) toast.error("Error de conexión") } } // PERSISTENCE LOGIC const DRAFT_KEY = 'receta_draft' // Load Draft useEffect(() => { const draft = localStorage.getItem(DRAFT_KEY) if (draft) { try { const parsed = JSON.parse(draft) // confirmation? Or just auto-load? User requested "persistir", implies auto. // We only restore if there are no specific URL params meant to override context (or we merge?) // For simplicity: Load draft. if (parsed.productorSeleccionado) setProductorSeleccionado(parsed.productorSeleccionado) if (parsed.establecimientoSeleccionado) setEstablecimientoSeleccionado(parsed.establecimientoSeleccionado) if (parsed.prestadorSeleccionado) setPrestadorSeleccionado(parsed.prestadorSeleccionado) // Load from draft if (parsed.campanaSeleccionada) setCampanaSeleccionada(parsed.campanaSeleccionada) if (parsed.cultivo) setCultivo(parsed.cultivo) if (parsed.lotesSeleccionados) setLotesSeleccionados(parsed.lotesSeleccionados) if (parsed.superficie) setSuperficie(parsed.superficie) if (parsed.superficieAplicable) { setSuperficieAplicable(parsed.superficieAplicable) setSuperficieAplicableInput(String(parsed.superficieAplicable)) superficieEditadaManualmente.current = true // preserve user's manual value on draft restore } if (parsed.labor) setLabor(parsed.labor) if (parsed.rinde) setRinde(parsed.rinde) if (parsed.momentoDia) setMomentoDia(parsed.momentoDia) if (parsed.productos) setProductos(parsed.productos) if (parsed.aplicarConViento) setAplicarConViento(parsed.aplicarConViento) if (parsed.vientoDireccion) setVientoDireccion(parsed.vientoDireccion) if (parsed.vientoIntensidad) setVientoIntensidad(parsed.vientoIntensidad) if (parsed.caudal) setCaudal(parsed.caudal) if (parsed.pastillas) setPastillas(parsed.pastillas) if (parsed.gotas) setGotas(parsed.gotas) if (parsed.deriva) setDeriva(parsed.deriva) if (parsed.observaciones) setObservaciones(parsed.observaciones) if (parsed.ingenieroResponsable) setIngenieroResponsable(parsed.ingenieroResponsable) if (parsed.matricula) setMatricula(parsed.matricula) } catch (e) { console.error("Error parsing draft", e) } } }, []) // Save Draft useEffect(() => { const dataToSave = { productorSeleccionado, establecimientoSeleccionado, campanaSeleccionada, cultivo, lotesSeleccionados, superficie, superficieAplicable, labor, rinde, momentoDia, productos, aplicarConViento, vientoDireccion, vientoIntensidad, caudal, pastillas, gotas, deriva, observaciones, ingenieroResponsable, matricula, prestadorSeleccionado } // Only save if there is at least some data to avoid overwriting with empty on initial mount race if (productorSeleccionado || establecimientoSeleccionado || productos.some(p => p.insumoId)) { localStorage.setItem(DRAFT_KEY, JSON.stringify(dataToSave)) } }, [ productorSeleccionado, establecimientoSeleccionado, prestadorSeleccionado, campanaSeleccionada, cultivo, lotesSeleccionados, superficie, superficieAplicable, labor, rinde, momentoDia, productos, aplicarConViento, vientoDireccion, vientoIntensidad, caudal, pastillas, gotas, deriva, observaciones, ingenieroResponsable, matricula ]) // Clear Draft on Success const clearDraft = () => { localStorage.removeItem(DRAFT_KEY) } const productorNombre = productores.find((p) => p.id === productorSeleccionado)?.nombre || "" const establecimientoData = todosEstablecimientos.find((e) => e.id === establecimientoSeleccionado) const [mounted, setMounted] = useState(false) useEffect(() => { setMounted(true) }, []) if (!mounted) { return (
) } return (

Nueva Receta Agronómica

Orden de trabajo profesional

AgroRecetas AgroRecetas
{numeroReceta}

{fechaActual}

{establecimientoSeleccionado && !campanasLoading && campanas.length === 0 && (

No hay campañas activas para este establecimiento.

)}

* Solo se listan productores con establecimientos vinculados.

{establecimientoData && (

{establecimientoData.ubicacion}

)}
Detalle del Lote y Productos Seleccione el lote y formule la mezcla para el mismo
{lotes.map((lote) => ( e.preventDefault()} onCheckedChange={(checked) => { setLotesSeleccionados(prev => checked ? [...prev, String(lote.id)] : prev.filter(id => id !== String(lote.id)) ) }} >
{lote.nombre} {lote.superficie} ha
))}
{ const val = e.target.value; const sanitized = val.replace(',', '.'); if (sanitized === '' || /^\d*\.?\d*$/.test(sanitized)) { setSuperficieAplicableInput(sanitized); superficieEditadaManualmente.current = true; } }} onBlur={(e) => { const num = parseFloat(e.target.value); const val = isNaN(num) ? 0 : num; setSuperficieAplicable(val); setSuperficieAplicableInput(val === 0 ? '' : String(val)); }} className="font-mono bg-primary/5 border-primary/20" />
{labor === "Cosecha" && (
{ const val = e.target.value.replace(',', '.'); if (val === '' || /^\d*\.?\d*$/.test(val)) { setRinde(val); } }} />
)}
{labor !== "Cosecha" && (

Tabla de Mezcla - Productos

Formulación y dosificación de insumos

Orden Producto Dosis Unidad Cantidad Total p.id)} strategy={verticalListSortingStrategy} > {productos.map((producto, index) => ( {(() => { const catFiltro = filtroCategoria[producto.id] || "todos" const insumosFiltrados = catFiltro === "todos" ? insumos : insumos.filter(i => i.categoriaId === catFiltro) return (
) })()}
{ const val = e.target.value; const sanitized = val.replace(',', '.'); if (sanitized === '' || /^\d*\.?\d*$/.test(sanitized)) { actualizarProducto(producto.id, "dosis", sanitized); } }} onBlur={(e) => { const num = parseFloat(e.target.value); actualizarProducto(producto.id, "dosis", isNaN(num) ? 0 : num); }} className="font-mono" />
{producto.unidad.split("/")[0]}
{productos.length > 1 && ( )}
))}
{superficieAplicable > 0 && (

Nota: Las cantidades totales se calculan automáticamente multiplicando la dosis por la superficie aplicable ({superficieAplicable} ha)

)}
)} Condiciones Ambientales y Técnicas Parámetros de aplicación y observaciones
{aplicarConViento === "SI" && (
{["N", "S", "E", "O", "NE", "NO", "SE", "SO"].map((dir) => ( ))} {vientoDireccion.length > 0 && ( ({vientoDireccion.join(", ")}) )}
)} {aplicarConViento === "SI" && (
)}
{ if (e.target.value.length <= 50) setCaudal(e.target.value) }} maxLength={50} placeholder="Ej: 100" type="text" className={caudal.length >= 50 ? 'border-red-500 focus-visible:ring-red-500' : ''} /> {caudal.length >= 50 && (

Has alcanzado el límite máximo de 50 caracteres.

)}
{ if (e.target.value.length <= 50) setPastillas(e.target.value) }} maxLength={50} placeholder="Ej: XR 110-03" type="text" className={pastillas.length >= 50 ? 'border-red-500 focus-visible:ring-red-500' : ''} /> {pastillas.length >= 50 && (

Has alcanzado el límite máximo de 50 caracteres.

)}
{ if (e.target.value.length <= 50) setGotas(e.target.value) }} maxLength={50} placeholder="Ej: 50-70" type="text" className={gotas.length >= 50 ? 'border-red-500 focus-visible:ring-red-500' : ''} /> {gotas.length >= 50 && (

Has alcanzado el límite máximo de 50 caracteres.

)}
{ if (e.target.value.length <= 50) setDeriva(e.target.value) }} maxLength={50} placeholder="Ej: Baja / Media" type="text" className={deriva.length >= 50 ? 'border-red-500 focus-visible:ring-red-500' : ''} /> {deriva.length >= 50 && (

Has alcanzado el límite máximo de 50 caracteres.

)}