Coverage for backend \ app \ Productos \ repositories \ productoRepository.py: 12.20%

123 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-29 16:13 -0500

1from app.Productos.models.productoModel import Producto 

2from app.Productos.schemas.productoSchemas import * 

3from app.Productos.models.categoriaProductoModel import CategoriaProducto 

4from app.Productos.models.proveedorModel import Proveedor 

5from sqlalchemy.orm import joinedload 

6 

7class ProductoRepository: 

8 def __init__(self, dbSession): 

9 self.dbSession = dbSession 

10 

11 def listarProductos(self): 

12 return self.dbSession.query(Producto).options(joinedload(Producto.categoria), joinedload(Producto.proveedor)).all() 

13 

14 def obtenerPorId(self, idProducto: int): 

15 return (self.dbSession.query(Producto) 

16 .options(joinedload(Producto.categoria), joinedload(Producto.proveedor)) 

17 .filter(Producto.idProducto == idProducto).first()) 

18 

19 def validarProductoParaVenta(self, idProducto: int, cantidadSolicitada: float): 

20 """Valida que el producto exista, esté activo y tenga stock suficiente. 

21 Devuelve un dict con 'error' clave si hay problema, o un dict con keys: 

22 - producto: objeto Producto 

23 - precio: float (precioUnitarioVenta) 

24 - tieneIva: bool 

25 - inventario: objeto Inventario (si existe) 

26 """ 

27 producto = self.obtenerPorId(idProducto) 

28 if not producto: 

29 return {"error": "producto_no_encontrado"} 

30 if not getattr(producto, "activoProducto", True): 

31 return {"error": "producto_inactivo"} 

32 try: 

33 from app.Inventario.repositories.inventarioRepository import InventarioRepository 

34 inv_repo = InventarioRepository(self.dbSession) 

35 inventario = inv_repo.obtenerPorProducto(idProducto) 

36 except Exception: 

37 inventario = None 

38 if not inventario or (inventario.cantidadDisponible or 0) < cantidadSolicitada: 

39 return {"error": "stock_insuficiente"} 

40 return { 

41 "producto": producto, 

42 "precio": float(producto.precioUnitarioVenta), 

43 "tieneIva": getattr(producto, "tieneIva", True), 

44 "inventario": inventario 

45 } 

46 

47 def crearProducto(self, producto: ProductoCrearSchema): 

48 # Verificar fk existencia 

49 categoria = self.dbSession.query(CategoriaProducto).filter(CategoriaProducto.idCategoriaProducto == producto.idCategoriaProducto).first() 

50 proveedor = self.dbSession.query(Proveedor).filter(Proveedor.idProveedor == producto.idProveedor).first() 

51 missing = [] 

52 if not categoria: 

53 missing.append("categoria") 

54 else: 

55 if not getattr(categoria, "activoCategoria", True): 

56 missing.append("categoria_inactiva") 

57 if not proveedor: 

58 missing.append("proveedor") 

59 else: 

60 if not getattr(proveedor, "activoProveedor", True): 

61 missing.append("proveedor_inactiva") 

62 # Validaciones de cantidades no negativas 

63 if getattr(producto, "cantidadDisponible", None) is not None and producto.cantidadDisponible < 0: 

64 missing.append("cantidad_disponible_negativa") 

65 if getattr(producto, "cantidadMinima", None) is not None and producto.cantidadMinima < 0: 

66 missing.append("cantidad_minima_negativa") 

67 if missing: 

68 return {"error": missing} 

69 # Crear producto (excluir campos de inventario si vienen) 

70 datos_producto = producto.model_dump(exclude={"cantidadDisponible","cantidadMinima"}) 

71 # Por defecto activoProducto = True al crear (no se acepta en el schema crear) 

72 datos_producto.setdefault("activoProducto", True) 

73 nuevo = Producto(**datos_producto) 

74 self.dbSession.add(nuevo) 

75 # Obtener id antes de commit 

76 self.dbSession.flush() 

77 # Crear inventario automáticamente si no existe 

78 try: 

79 from app.Inventario.models.inventarioModel import Inventario 

80 existe_inv = self.dbSession.query(Inventario).filter(Inventario.idProducto == nuevo.idProducto).first() 

81 if not existe_inv: 

82 cantidadDisponible = getattr(producto, "cantidadDisponible", None) 

83 cantidadMinima = getattr(producto, "cantidadMinima", None) 

84 inv_datos = { 

85 "idProducto": nuevo.idProducto, 

86 "cantidadDisponible": cantidadDisponible if cantidadDisponible is not None else 0, 

87 "cantidadMinima": cantidadMinima if cantidadMinima is not None else 0, 

88 "activoInventario": True 

89 } 

90 nuevo_inv = Inventario(**inv_datos) 

91 self.dbSession.add(nuevo_inv) 

92 inventario_creado = True 

93 else: 

94 inventario_creado = False 

95 except Exception: 

96 inventario_creado = False 

97 self.dbSession.commit() 

98 self.dbSession.refresh(nuevo) 

99 # devolver flag para que el servicio pueda ajustar el mensaje 

100 return (nuevo, inventario_creado) 

101 

102 def modificarProducto(self, idProducto: int, productoActualizar: ProductoActualizarSchema): 

103 producto = self.obtenerPorId(idProducto) 

104 if not producto: 

105 return None 

106 datos = productoActualizar.model_dump(exclude_unset=True) 

107 camposValidos = ["idCategoriaProducto","idProveedor","nombreProducto","descripcionProducto","precioUnitarioVenta","precioUnitarioCompra","tieneIva","activoProducto"] 

108 missing = [] 

109 for campo, valor in datos.items(): 

110 if campo in camposValidos: 

111 if campo == "idCategoriaProducto": 

112 cat = self.dbSession.query(CategoriaProducto).filter(CategoriaProducto.idCategoriaProducto == valor).first() 

113 if not cat: 

114 missing.append("categoria") 

115 continue 

116 if campo == "idProveedor": 

117 prov = self.dbSession.query(Proveedor).filter(Proveedor.idProveedor == valor).first() 

118 if not prov: 

119 missing.append("proveedor") 

120 continue 

121 setattr(producto, campo, valor) 

122 if missing: 

123 # quitar duplicados y devolver la lista de errores 

124 return {"error": list(dict.fromkeys(missing))} 

125 self.dbSession.commit() 

126 self.dbSession.refresh(producto) 

127 return producto 

128 

129 def deshabilitarProducto(self, idProducto: int): 

130 producto = self.obtenerPorId(idProducto) 

131 if not producto: 

132 return None 

133 producto.activoProducto = False 

134 self.dbSession.commit() 

135 self.dbSession.refresh(producto) 

136 return producto 

137 

138 def crearProductosIniciales(self): 

139 productos = [ 

140 {"idCategoriaProducto": 1, "idProveedor": 1, "nombreProducto": "Cola 2 Litros", "descripcionProducto": "Bebida gaseosa sabor cola", "precioUnitarioVenta": 2.50, "precioUnitarioCompra": 1.80, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 100, "cantidadMinima": 10}, 

141 {"idCategoriaProducto": 1, "idProveedor": 1, "nombreProducto": "Jugo de Naranja 1L", "descripcionProducto": "Jugo natural pasteurizado", "precioUnitarioVenta": 1.80, "precioUnitarioCompra": 1.20, "tieneIva": False, "activoProducto": True, "cantidadDisponible": 80, "cantidadMinima": 10}, 

142 {"idCategoriaProducto": 1, "idProveedor": 1, "nombreProducto": "Agua Mineral 600ml", "descripcionProducto": "Agua sin gas", "precioUnitarioVenta": 0.70, "precioUnitarioCompra": 0.40, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 200, "cantidadMinima": 20}, 

143 {"idCategoriaProducto": 1, "idProveedor": 1, "nombreProducto": "Bebida Energética", "descripcionProducto": "Bebida energizante lata", "precioUnitarioVenta": 2.20, "precioUnitarioCompra": 1.50, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 60, "cantidadMinima": 8}, 

144 {"idCategoriaProducto": 1, "idProveedor": 1, "nombreProducto": "Té Frío Limón", "descripcionProducto": "Té frío sabor limón", "precioUnitarioVenta": 1.50, "precioUnitarioCompra": 1.00, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 90, "cantidadMinima": 10}, 

145 

146 {"idCategoriaProducto": 2, "idProveedor": 2, "nombreProducto": "Papas Fritas Clásicas", "descripcionProducto": "Papas fritas bolsa mediana", "precioUnitarioVenta": 1.25, "precioUnitarioCompra": 0.80, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 120, "cantidadMinima": 15}, 

147 {"idCategoriaProducto": 2, "idProveedor": 2, "nombreProducto": "Galletas de Chocolate", "descripcionProducto": "Galletas rellenas", "precioUnitarioVenta": 1.10, "precioUnitarioCompra": 0.75, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 150, "cantidadMinima": 20}, 

148 {"idCategoriaProducto": 2, "idProveedor": 2, "nombreProducto": "Maní Salado", "descripcionProducto": "Maní tostado y salado", "precioUnitarioVenta": 0.90, "precioUnitarioCompra": 0.50, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 80, "cantidadMinima": 10}, 

149 {"idCategoriaProducto": 2, "idProveedor": 2, "nombreProducto": "Chifles", "descripcionProducto": "Plátano frito en rodajas", "precioUnitarioVenta": 1.00, "precioUnitarioCompra": 0.65, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 60, "cantidadMinima": 10}, 

150 {"idCategoriaProducto": 2, "idProveedor": 2, "nombreProducto": "Barra de Cereal", "descripcionProducto": "Barra energética", "precioUnitarioVenta": 0.85, "precioUnitarioCompra": 0.55, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 200, "cantidadMinima": 20}, 

151 

152 {"idCategoriaProducto": 3, "idProveedor": 3, "nombreProducto": "Arroz 5 Libras", "descripcionProducto": "Arroz blanco grano largo", "precioUnitarioVenta": 3.20, "precioUnitarioCompra": 2.60, "tieneIva": False, "activoProducto": True, "cantidadDisponible": 40, "cantidadMinima": 10}, 

153 {"idCategoriaProducto": 3, "idProveedor": 3, "nombreProducto": "Azúcar 2 Libras", "descripcionProducto": "Azúcar refinada", "precioUnitarioVenta": 1.90, "precioUnitarioCompra": 1.40, "tieneIva": False, "activoProducto": True, "cantidadDisponible": 60, "cantidadMinima": 10}, 

154 {"idCategoriaProducto": 3, "idProveedor": 3, "nombreProducto": "Aceite Vegetal 1L", "descripcionProducto": "Aceite comestible", "precioUnitarioVenta": 3.80, "precioUnitarioCompra": 3.10, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 75, "cantidadMinima": 10}, 

155 {"idCategoriaProducto": 3, "idProveedor": 3, "nombreProducto": "Fideos Spaghetti", "descripcionProducto": "Pasta seca", "precioUnitarioVenta": 1.40, "precioUnitarioCompra": 0.95, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 120, "cantidadMinima": 20}, 

156 {"idCategoriaProducto": 3, "idProveedor": 3, "nombreProducto": "Atún en Lata", "descripcionProducto": "Atún en aceite", "precioUnitarioVenta": 1.75, "precioUnitarioCompra": 1.20, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 90, "cantidadMinima": 15}, 

157 

158 {"idCategoriaProducto": 4, "idProveedor": 4, "nombreProducto": "Detergente en Polvo", "descripcionProducto": "Detergente multiuso", "precioUnitarioVenta": 4.50, "precioUnitarioCompra": 3.60, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 50, "cantidadMinima": 10}, 

159 {"idCategoriaProducto": 4, "idProveedor": 4, "nombreProducto": "Cloro 1L", "descripcionProducto": "Desinfectante", "precioUnitarioVenta": 1.20, "precioUnitarioCompra": 0.80, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 100, "cantidadMinima": 20}, 

160 {"idCategoriaProducto": 4, "idProveedor": 4, "nombreProducto": "Esponjas", "descripcionProducto": "Esponjas para lavar", "precioUnitarioVenta": 1.00, "precioUnitarioCompra": 0.60, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 250, "cantidadMinima": 30}, 

161 {"idCategoriaProducto": 4, "idProveedor": 4, "nombreProducto": "Limpiavidrios", "descripcionProducto": "Limpieza de superficies", "precioUnitarioVenta": 2.30, "precioUnitarioCompra": 1.70, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 60, "cantidadMinima": 10}, 

162 {"idCategoriaProducto": 4, "idProveedor": 4, "nombreProducto": "Desinfectante Piso", "descripcionProducto": "Aroma floral", "precioUnitarioVenta": 2.80, "precioUnitarioCompra": 2.10, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 40, "cantidadMinima": 8}, 

163 

164 {"idCategoriaProducto": 5, "idProveedor": 5, "nombreProducto": "Jabón de Baño", "descripcionProducto": "Jabón antibacterial", "precioUnitarioVenta": 1.10, "precioUnitarioCompra": 0.70, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 180, "cantidadMinima": 30}, 

165 {"idCategoriaProducto": 5, "idProveedor": 5, "nombreProducto": "Shampoo", "descripcionProducto": "Shampoo familiar", "precioUnitarioVenta": 4.20, "precioUnitarioCompra": 3.30, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 45, "cantidadMinima": 8}, 

166 {"idCategoriaProducto": 5, "idProveedor": 5, "nombreProducto": "Pasta Dental", "descripcionProducto": "Protección dental", "precioUnitarioVenta": 2.50, "precioUnitarioCompra": 1.90, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 110, "cantidadMinima": 20}, 

167 {"idCategoriaProducto": 5, "idProveedor": 5, "nombreProducto": "Papel Higiénico", "descripcionProducto": "Paquete 4 rollos", "precioUnitarioVenta": 3.60, "precioUnitarioCompra": 2.80, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 70, "cantidadMinima": 10}, 

168 {"idCategoriaProducto": 5, "idProveedor": 5, "nombreProducto": "Desodorante", "descripcionProducto": "Protección 24 horas", "precioUnitarioVenta": 3.90, "precioUnitarioCompra": 3.00, "tieneIva": True, "activoProducto": True, "cantidadDisponible": 65, "cantidadMinima": 10}, 

169 ] 

170 

171 for p in productos: 

172 existe = self.dbSession.query(Producto).filter(Producto.nombreProducto == p["nombreProducto"]).first() 

173 if not existe: 

174 # Verificar fk antes de crear 

175 cat = self.dbSession.query(CategoriaProducto).filter(CategoriaProducto.idCategoriaProducto == p["idCategoriaProducto"]).first() 

176 prov = self.dbSession.query(Proveedor).filter(Proveedor.idProveedor == p["idProveedor"]).first() 

177 if cat and prov: 

178 # Excluir campos de inventario al crear Producto 

179 datos_producto = {k: v for k, v in p.items() if k not in ("cantidadDisponible", "cantidadMinima")} 

180 datos_producto.setdefault("activoProducto", True) 

181 nuevo = Producto(**datos_producto) 

182 self.dbSession.add(nuevo) 

183 # flush para obtener el idProducto y crear inventario inicial si no existe 

184 self.dbSession.flush() 

185 try: 

186 from app.Inventario.models.inventarioModel import Inventario 

187 existe_inv = self.dbSession.query(Inventario).filter(Inventario.idProducto == nuevo.idProducto).first() 

188 if not existe_inv: 

189 inv = Inventario(idProducto=nuevo.idProducto, cantidadDisponible=p.get("cantidadDisponible", 0), cantidadMinima=p.get("cantidadMinima", 0), activoInventario=True) 

190 self.dbSession.add(inv) 

191 except Exception: 

192 # Si Inventario no existe o hay problema, skip 

193 pass 

194 print("Productos por defecto creados...!") 

195 self.dbSession.commit() 

196 self.dbSession.close()