/** * Composable para gesti贸n de lotes y operaciones de trazabilidad */ export interface Lote { id: string codigo: string | null tipo: string fecha_creado: string lugar_id: number | null cantidad_kg: number | null meta: Record | null } export interface Operacion { id: string tipo: string fecha: string lugar_id: number | null meta: Record | null } export interface TrazabilidadRow { lote_id: string codigo: string | null tipo: string cantidad_kg: number | null operacion_id: string | null operacion_tipo: string | null profundidad: number parent_lote_id: string | null } export const useLotes = () => { console.log('馃數 useLotes: Composable llamado, process.client:', process.client) // useToast solo funciona en el cliente, no en SSR const toast = process.client ? useToast() : null console.log('馃數 useLotes: Toast inicializado:', !!toast) // ===================================================== // FUNCIONES PARA LOTES // ===================================================== /** * Obtiene todos los lotes con filtros opcionales */ const fetchLotes = async (filtros?: { tipo?: string limit?: number offset?: number }) => { console.log('馃數 fetchLotes: Iniciando, filtros:', filtros) try { const query = new URLSearchParams() if (filtros?.tipo) query.append('tipo', filtros.tipo) if (filtros?.limit) query.append('limit', filtros.limit.toString()) if (filtros?.offset) query.append('offset', filtros.offset.toString()) const queryString = query.toString() const url = `/api/lotes${queryString ? `?${queryString}` : ''}` console.log('馃數 fetchLotes: URL construida:', url) console.log('馃數 fetchLotes: Llamando a useFetch...') const { data, error } = await useFetch<{ success: boolean data: Lote[] count: number }>(url) console.log('馃數 fetchLotes: useFetch completado, data:', !!data.value, 'error:', !!error.value) if (error.value) { throw new Error(error.value.message || 'Error obteniendo lotes') } return data.value?.data || [] } catch (err: any) { console.error('Error fetching lotes:', err) toast?.add({ title: 'Error', description: err.message || 'Error obteniendo lotes', color: 'red', }) return [] } } /** * Obtiene un lote por su ID */ const fetchLoteById = async (id: string) => { try { const { data, error } = await useFetch<{ success: boolean data: Lote }>(`/api/lotes/${id}`) if (error.value) { throw new Error(error.value.message || 'Error obteniendo lote') } return data.value?.data || null } catch (err: any) { console.error('Error fetching lote:', err) toast?.add({ title: 'Error', description: err.message || 'Error obteniendo lote', color: 'red', }) return null } } /** * Crea un nuevo lote */ const createLote = async (loteData: { codigo?: string tipo: string cantidad_kg?: number lugar_id?: number meta?: Record }) => { try { const { data, error } = await useFetch<{ success: boolean data: Lote }>('/api/lotes', { method: 'POST', body: loteData, }) if (error.value) { throw new Error(error.value.message || 'Error creando lote') } toast?.add({ title: '脡xito', description: 'Lote creado correctamente', color: 'green', }) return data.value?.data || null } catch (err: any) { console.error('Error creating lote:', err) toast?.add({ title: 'Error', description: err.message || 'Error creando lote', color: 'red', }) return null } } /** * Actualiza un lote existente */ const updateLote = async ( id: string, updates: Partial<{ codigo: string | null tipo: string cantidad_kg: number | null lugar_id: number | null meta: Record | null }> ) => { try { const { data, error } = await useFetch<{ success: boolean data: Lote }>(`/api/lotes/${id}`, { method: 'PATCH', body: updates, }) if (error.value) { throw new Error(error.value.message || 'Error actualizando lote') } toast?.add({ title: '脡xito', description: 'Lote actualizado correctamente', color: 'green', }) return data.value?.data || null } catch (err: any) { console.error('Error updating lote:', err) toast?.add({ title: 'Error', description: err.message || 'Error actualizando lote', color: 'red', }) return null } } /** * Elimina un lote */ const deleteLote = async (id: string) => { try { const { error } = await useFetch(`/api/lotes/${id}`, { method: 'DELETE', }) if (error.value) { throw new Error(error.value.message || 'Error eliminando lote') } toast?.add({ title: '脡xito', description: 'Lote eliminado correctamente', color: 'green', }) return true } catch (err: any) { console.error('Error deleting lote:', err) toast?.add({ title: 'Error', description: err.message || 'Error eliminando lote', color: 'red', }) return false } } /** * Obtiene el historial completo de trazabilidad de un lote */ const fetchTrazabilidad = async (id: string) => { try { const { data, error } = await useFetch<{ success: boolean data: { historial: TrazabilidadRow[] estadisticas: { total_ancestros: number profundidad_maxima: number kg_iniciales: number } } }>(`/api/lotes/${id}/trazabilidad`) if (error.value) { throw new Error(error.value.message || 'Error obteniendo trazabilidad') } return data.value?.data || null } catch (err: any) { console.error('Error fetching trazabilidad:', err) toast?.add({ title: 'Error', description: err.message || 'Error obteniendo trazabilidad', color: 'red', }) return null } } // ===================================================== // FUNCIONES PARA OPERACIONES // ===================================================== /** * Obtiene todas las operaciones con filtros opcionales */ const fetchOperaciones = async (filtros?: { tipo?: string limit?: number offset?: number }) => { try { const query = new URLSearchParams() if (filtros?.tipo) query.append('tipo', filtros.tipo) if (filtros?.limit) query.append('limit', filtros.limit.toString()) if (filtros?.offset) query.append('offset', filtros.offset.toString()) const queryString = query.toString() const url = `/api/operaciones${queryString ? `?${queryString}` : ''}` const { data, error } = await useFetch<{ success: boolean data: Operacion[] count: number }>(url) if (error.value) { throw new Error(error.value.message || 'Error obteniendo operaciones') } return data.value?.data || [] } catch (err: any) { console.error('Error fetching operaciones:', err) toast?.add({ title: 'Error', description: err.message || 'Error obteniendo operaciones', color: 'red', }) return [] } } /** * Crea una nueva operaci贸n con sus lotes inputs/outputs */ const createOperacion = async (operacionData: { tipo: string fecha?: string lugar_id?: number meta?: Record inputs: Array<{ lote_id: string; cantidad_kg?: number }> outputs: Array<{ codigo?: string tipo: string cantidad_kg?: number meta?: Record }> }) => { try { const { data, error } = await useFetch<{ success: boolean data: { operacion: Operacion lotes_creados: Lote[] } }>('/api/operaciones', { method: 'POST', body: operacionData, }) if (error.value) { throw new Error(error.value.message || 'Error creando operaci贸n') } toast?.add({ title: '脡xito', description: 'Operaci贸n creada correctamente', color: 'green', }) return data.value?.data || null } catch (err: any) { console.error('Error creating operacion:', err) toast?.add({ title: 'Error', description: err.message || 'Error creando operaci贸n', color: 'red', }) return null } } // ===================================================== // CONSTANTES 脷TILES // ===================================================== const TIPOS_LOTE = [ { value: 'uva', label: 'Uva' }, { value: 'despulpado_primera', label: 'Despulpado Primera' }, { value: 'despulpado_segunda', label: 'Despulpado Segunda' }, { value: 'despulpado_rechazos', label: 'Despulpado Rechazos' }, { value: 'oreado', label: 'Oreado' }, { value: 'presecado', label: 'Presecado' }, { value: 'reposo', label: 'Reposo' }, { value: 'secado', label: 'Secado' }, ] const TIPOS_OPERACION = [ { value: 'ingreso', label: 'Ingreso', icon: 'i-heroicons-arrow-down-tray' }, { value: 'despulpado', label: 'Despulpado', icon: 'i-heroicons-beaker' }, { value: 'oreado', label: 'Oreado', icon: 'i-heroicons-sun' }, { value: 'presecado', label: 'Presecado', icon: 'i-heroicons-fire' }, { value: 'reposo', label: 'Reposo', icon: 'i-heroicons-pause' }, { value: 'secado', label: 'Secado', icon: 'i-heroicons-check-circle' }, { value: 'traslado', label: 'Traslado', icon: 'i-heroicons-arrow-right' }, { value: 'mezcla', label: 'Mezcla', icon: 'i-heroicons-beaker' }, { value: 'ajuste_merma', label: 'Ajuste Merma', icon: 'i-heroicons-adjustments-horizontal' }, { value: 'ajuste_cantidad', label: 'Ajuste Cantidad', icon: 'i-heroicons-calculator' }, { value: 'ajuste_tipo', label: 'Ajuste Tipo', icon: 'i-heroicons-pencil' }, ] return { // Lotes fetchLotes, fetchLoteById, createLote, updateLote, deleteLote, fetchTrazabilidad, // Operaciones fetchOperaciones, createOperacion, // Constantes TIPOS_LOTE, TIPOS_OPERACION, } }