From 10c779858a15219460ea7bd7d5c37d5b597fb823 Mon Sep 17 00:00:00 2001 From: Urtzi Alfaro Date: Sat, 13 Dec 2025 16:49:04 +0100 Subject: [PATCH] Fix enum mismatch: Update Python enums and seed data to match database uppercase values - Fixed ProductType enum values from lowercase to uppercase (INGREDIENT, FINISHED_PRODUCT) - Fixed UnitOfMeasure enum values from lowercase/abbreviated to uppercase (KILOGRAMS, LITERS, etc.) - Fixed IngredientCategory enum values from lowercase to uppercase (FLOUR, YEAST, etc.) - Fixed ProductCategory enum values from lowercase to uppercase (BREAD, CROISSANTS, etc.) - Updated seed data files to use correct uppercase enum values - Fixed hardcoded enum references throughout the codebase - This resolves the InvalidTextRepresentationError when inserting inventory data Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe --- .../inventory/app/api/inventory_operations.py | 4 +- services/inventory/app/models/inventory.py | 66 +- .../app/repositories/dashboard_repository.py | 4 +- .../app/repositories/ingredient_repository.py | 118 +- .../app/services/inventory_service.py | 8 +- .../enterprise/parent/03-inventory.json | 181 +++ .../fixtures/professional/03-inventory.json | 1020 +++++++++++++++++ 7 files changed, 1354 insertions(+), 47 deletions(-) create mode 100644 shared/demo/fixtures/enterprise/parent/03-inventory.json create mode 100644 shared/demo/fixtures/professional/03-inventory.json diff --git a/services/inventory/app/api/inventory_operations.py b/services/inventory/app/api/inventory_operations.py index 31db657d..af9fb4e7 100644 --- a/services/inventory/app/api/inventory_operations.py +++ b/services/inventory/app/api/inventory_operations.py @@ -280,8 +280,8 @@ async def classify_products_batch( )) # Analyze business model - ingredient_count = sum(1 for s in suggestions if s.product_type.value == 'ingredient') - finished_count = sum(1 for s in suggestions if s.product_type.value == 'finished_product') + ingredient_count = sum(1 for s in suggestions if s.product_type.value == 'INGREDIENT') + finished_count = sum(1 for s in suggestions if s.product_type.value == 'FINISHED_PRODUCT') semi_finished_count = sum(1 for s in suggestions if 'semi' in s.suggested_name.lower() or 'frozen' in s.suggested_name.lower() or 'pre' in s.suggested_name.lower()) total = len(suggestions) ingredient_ratio = ingredient_count / total if total > 0 else 0 diff --git a/services/inventory/app/models/inventory.py b/services/inventory/app/models/inventory.py index 9edb0c22..85d68837 100644 --- a/services/inventory/app/models/inventory.py +++ b/services/inventory/app/models/inventory.py @@ -17,51 +17,51 @@ from shared.database.base import Base class UnitOfMeasure(enum.Enum): """Standard units of measure for ingredients""" - KILOGRAMS = "kg" - GRAMS = "g" - LITERS = "l" - MILLILITERS = "ml" - UNITS = "units" - PIECES = "pcs" - PACKAGES = "pkg" - BAGS = "bags" - BOXES = "boxes" + KILOGRAMS = "KILOGRAMS" + GRAMS = "GRAMS" + LITERS = "LITERS" + MILLILITERS = "MILLILITERS" + UNITS = "UNITS" + PIECES = "PIECES" + PACKAGES = "PACKAGES" + BAGS = "BAGS" + BOXES = "BOXES" class IngredientCategory(enum.Enum): """Bakery ingredient categories""" - FLOUR = "flour" - YEAST = "yeast" - DAIRY = "dairy" - EGGS = "eggs" - SUGAR = "sugar" - FATS = "fats" - SALT = "salt" - SPICES = "spices" - ADDITIVES = "additives" - PACKAGING = "packaging" - CLEANING = "cleaning" - OTHER = "other" + FLOUR = "FLOUR" + YEAST = "YEAST" + DAIRY = "DAIRY" + EGGS = "EGGS" + SUGAR = "SUGAR" + FATS = "FATS" + SALT = "SALT" + SPICES = "SPICES" + ADDITIVES = "ADDITIVES" + PACKAGING = "PACKAGING" + CLEANING = "CLEANING" + OTHER = "OTHER" class ProductCategory(enum.Enum): """Finished bakery product categories for retail/distribution model""" - BREAD = "bread" - CROISSANTS = "croissants" - PASTRIES = "pastries" - CAKES = "cakes" - COOKIES = "cookies" - MUFFINS = "muffins" - SANDWICHES = "sandwiches" - SEASONAL = "seasonal" - BEVERAGES = "beverages" - OTHER_PRODUCTS = "other_products" + BREAD = "BREAD" + CROISSANTS = "CROISSANTS" + PASTRIES = "PASTRIES" + CAKES = "CAKES" + COOKIES = "COOKIES" + MUFFINS = "MUFFINS" + SANDWICHES = "SANDWICHES" + SEASONAL = "SEASONAL" + BEVERAGES = "BEVERAGES" + OTHER_PRODUCTS = "OTHER_PRODUCTS" class ProductType(enum.Enum): """Type of product in inventory""" - INGREDIENT = "ingredient" # Raw materials (flour, yeast, etc.) - FINISHED_PRODUCT = "finished_product" # Ready-to-sell items (bread, croissants, etc.) + INGREDIENT = "INGREDIENT" # Raw materials (flour, yeast, etc.) + FINISHED_PRODUCT = "FINISHED_PRODUCT" # Ready-to-sell items (bread, croissants, etc.) class ProductionStage(enum.Enum): diff --git a/services/inventory/app/repositories/dashboard_repository.py b/services/inventory/app/repositories/dashboard_repository.py index 8fc4fe51..945cd705 100644 --- a/services/inventory/app/repositories/dashboard_repository.py +++ b/services/inventory/app/repositories/dashboard_repository.py @@ -26,8 +26,8 @@ class DashboardRepository: query = text(""" SELECT COUNT(*) as total_ingredients, - COUNT(CASE WHEN product_type::text = 'finished_product' THEN 1 END) as finished_products, - COUNT(CASE WHEN product_type::text = 'ingredient' THEN 1 END) as raw_ingredients, + COUNT(CASE WHEN product_type::text = 'FINISHED_PRODUCT' THEN 1 END) as finished_products, + COUNT(CASE WHEN product_type::text = 'INGREDIENT' THEN 1 END) as raw_ingredients, COUNT(DISTINCT st.supplier_id) as supplier_count, AVG(CASE WHEN s.available_quantity IS NOT NULL THEN s.available_quantity ELSE 0 END) as avg_stock_level FROM ingredients i diff --git a/services/inventory/app/repositories/ingredient_repository.py b/services/inventory/app/repositories/ingredient_repository.py index 64b336d9..e847873d 100644 --- a/services/inventory/app/repositories/ingredient_repository.py +++ b/services/inventory/app/repositories/ingredient_repository.py @@ -40,7 +40,7 @@ class IngredientRepository(BaseRepository[Ingredient, IngredientCreate, Ingredie ingredient_name=create_data.get('name'), tenant_id=tenant_id ) - product_type_value = 'ingredient' + product_type_value = 'INGREDIENT' if 'product_type' in create_data: from app.models.inventory import ProductType @@ -73,7 +73,7 @@ class IngredientRepository(BaseRepository[Ingredient, IngredientCreate, Ingredie if 'category' in create_data: category_value = create_data.pop('category') - if product_type_value == 'finished_product' or product_type_value == 'FINISHED_PRODUCT': + if product_type_value == 'FINISHED_PRODUCT': # Map to product_category for finished products from app.models.inventory import ProductCategory if category_value: @@ -166,15 +166,15 @@ class IngredientRepository(BaseRepository[Ingredient, IngredientCreate, Ingredie # Handle category mapping based on product type if 'category' in update_data: category_value = update_data.pop('category') - product_type_value = update_data.get('product_type', 'ingredient') + product_type_value = update_data.get('product_type', 'INGREDIENT') # Get current product if we need to determine type if 'product_type' not in update_data: current_record = await self.get_by_id(record_id) if current_record: - product_type_value = current_record.product_type.value if current_record.product_type else 'ingredient' + product_type_value = current_record.product_type.value if current_record.product_type else 'INGREDIENT' - if product_type_value == 'finished_product' or product_type_value == 'FINISHED_PRODUCT': + if product_type_value == 'FINISHED_PRODUCT': # Map to product_category for finished products from app.models.inventory import ProductCategory if category_value: @@ -559,4 +559,110 @@ class IngredientRepository(BaseRepository[Ingredient, IngredientCreate, Ingredie except Exception as e: logger.error("Failed to get active tenants from ingredients", error=str(e)) - return [] \ No newline at end of file + return [] + + async def get_critical_stock_shortages(self) -> List[Dict[str, Any]]: + """ + Get critical stock shortages across all tenants using CTE analysis. + Returns ingredients that are critically low on stock. + """ + try: + from sqlalchemy import text + query = text(""" + WITH stock_analysis AS ( + SELECT + i.id as ingredient_id, + i.name as ingredient_name, + i.tenant_id, + i.reorder_point, + COALESCE(SUM(s.current_quantity), 0) as current_quantity, + i.low_stock_threshold, + GREATEST(0, i.low_stock_threshold - COALESCE(SUM(s.current_quantity), 0)) as shortage_amount, + CASE + WHEN COALESCE(SUM(s.current_quantity), 0) < i.low_stock_threshold THEN 'critical' + WHEN COALESCE(SUM(s.current_quantity), 0) < i.low_stock_threshold * 1.2 THEN 'low' + ELSE 'normal' + END as status + FROM ingredients i + LEFT JOIN stock s ON s.ingredient_id = i.id AND s.is_available = true + WHERE i.is_active = true + GROUP BY i.id, i.name, i.tenant_id, i.reorder_point, i.low_stock_threshold + ) + SELECT + ingredient_id, + ingredient_name, + tenant_id, + current_quantity, + reorder_point, + shortage_amount + FROM stock_analysis + WHERE status = 'critical' + ORDER BY shortage_amount DESC + """) + + result = await self.session.execute(query) + rows = result.fetchall() + + shortages = [] + for row in rows: + shortages.append({ + 'ingredient_id': row.ingredient_id, + 'ingredient_name': row.ingredient_name, + 'tenant_id': row.tenant_id, + 'current_quantity': float(row.current_quantity) if row.current_quantity else 0, + 'required_quantity': float(row.reorder_point) if row.reorder_point else 0, + 'shortage_amount': float(row.shortage_amount) if row.shortage_amount else 0 + }) + + return shortages + + except Exception as e: + logger.error("Failed to get critical stock shortages", error=str(e)) + raise + + async def get_stock_issues(self, tenant_id: UUID) -> List[Dict[str, Any]]: + """ + Get stock level issues with CTE analysis for a specific tenant + Returns list of critical, low, and overstock situations + """ + try: + from sqlalchemy import text + query = text(""" + WITH stock_analysis AS ( + SELECT + i.id, i.name, i.tenant_id, + COALESCE(SUM(s.current_quantity), 0) as current_stock, + i.low_stock_threshold as minimum_stock, + i.max_stock_level as maximum_stock, + i.reorder_point, + 0 as tomorrow_needed, + 0 as avg_daily_usage, + 7 as lead_time_days, + CASE + WHEN COALESCE(SUM(s.current_quantity), 0) < i.low_stock_threshold THEN 'critical' + WHEN COALESCE(SUM(s.current_quantity), 0) < i.low_stock_threshold * 1.2 THEN 'low' + WHEN i.max_stock_level IS NOT NULL AND COALESCE(SUM(s.current_quantity), 0) > i.max_stock_level THEN 'overstock' + ELSE 'normal' + END as status, + GREATEST(0, i.low_stock_threshold - COALESCE(SUM(s.current_quantity), 0)) as shortage_amount + FROM ingredients i + LEFT JOIN stock s ON s.ingredient_id = i.id AND s.is_available = true + WHERE i.tenant_id = :tenant_id AND i.is_active = true + GROUP BY i.id, i.name, i.tenant_id, i.low_stock_threshold, i.max_stock_level, i.reorder_point + ) + SELECT * FROM stock_analysis WHERE status != 'normal' + ORDER BY + CASE status + WHEN 'critical' THEN 1 + WHEN 'low' THEN 2 + WHEN 'overstock' THEN 3 + END, + shortage_amount DESC + """) + + result = await self.session.execute(query, {"tenant_id": tenant_id}) + return [dict(row._mapping) for row in result.fetchall()] + + except Exception as e: + logger.error("Failed to get stock issues", error=str(e), tenant_id=str(tenant_id)) + raise \ No newline at end of file diff --git a/services/inventory/app/services/inventory_service.py b/services/inventory/app/services/inventory_service.py index 57c2aeba..cfa1ea73 100644 --- a/services/inventory/app/services/inventory_service.py +++ b/services/inventory/app/services/inventory_service.py @@ -1069,14 +1069,14 @@ class InventoryService: ingredient_create = IngredientCreate( name=ingredient_data.get('name'), - product_type=ingredient_data.get('type', 'finished_product'), - unit_of_measure=ingredient_data.get('unit', 'units'), + product_type=ingredient_data.get('type', 'FINISHED_PRODUCT'), + unit_of_measure=ingredient_data.get('unit', 'UNITS'), low_stock_threshold=ingredient_data.get('current_stock', 0), reorder_point=max(ingredient_data.get('reorder_point', 1), ingredient_data.get('current_stock', 0) + 1), average_cost=ingredient_data.get('cost_per_unit', 0.0), - ingredient_category=ingredient_data.get('category') if ingredient_data.get('type') == 'ingredient' else None, - product_category=ingredient_data.get('category') if ingredient_data.get('type') == 'finished_product' else None + ingredient_category=ingredient_data.get('category') if ingredient_data.get('type') == 'INGREDIENT' else None, + product_category=ingredient_data.get('category') if ingredient_data.get('type') == 'FINISHED_PRODUCT' else None ) ingredient = await repository.create_ingredient(ingredient_create, tenant_id) diff --git a/shared/demo/fixtures/enterprise/parent/03-inventory.json b/shared/demo/fixtures/enterprise/parent/03-inventory.json new file mode 100644 index 00000000..d5afa82f --- /dev/null +++ b/shared/demo/fixtures/enterprise/parent/03-inventory.json @@ -0,0 +1,181 @@ +{ + "ingredients": [ + { + "id": "10000000-0000-0000-0000-000000000001", + "tenant_id": "80000000-0000-4000-a000-000000000001", + "name": "Harina de Trigo T55 - Enterprise Grade", + "sku": "HAR-T55-ENT-001", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "FLOUR", + "product_category": "BREAD", + "subcategory": null, + "description": "Premium harina de trigo tipo 55 para uso en todas las ubicaciones", + "brand": "Molinos San José - Enterprise", + "unit_of_measure": "KILOGRAMS", + "package_size": null, + "average_cost": 0.80, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 500.0, + "reorder_point": 750.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": null, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": false, + "allergen_info": [ + "gluten" + ], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "d2e3f4a5-b6c7-48d9-e0f1-a2b3c4d5e6f7", + "enterprise_shared": true, + "shared_locations": ["Madrid Centro", "Barcelona Gràcia", "Valencia Ruzafa"] + }, + { + "id": "10000000-0000-0000-0000-000000000002", + "tenant_id": "80000000-0000-4000-a000-000000000001", + "name": "Mantequilla Francesa AOP - Enterprise", + "sku": "MAN-FRA-ENT-001", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "DAIRY", + "product_category": "PASTRY", + "subcategory": null, + "description": "Mantequilla francesa AOP para uso en croissants y pastelería fina", + "brand": "Lescure - Enterprise", + "unit_of_measure": "KILOGRAMS", + "package_size": null, + "average_cost": 4.20, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 200.0, + "reorder_point": 300.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": null, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": true, + "allergen_info": [ + "milk" + ], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "d2e3f4a5-b6c7-48d9-e0f1-a2b3c4d5e6f7", + "enterprise_shared": true, + "shared_locations": ["Madrid Centro", "Barcelona Gràcia", "Valencia Ruzafa"] + }, + { + "id": "20000000-0000-0000-0000-000000000001", + "tenant_id": "80000000-0000-4000-a000-000000000001", + "name": "Baguette Premium - Enterprise Standard", + "sku": "BAG-PRE-ENT-001", + "barcode": null, + "product_type": "FINISHED_PRODUCT", + "ingredient_category": null, + "product_category": "BREAD", + "subcategory": "FRENCH", + "description": "Baguette premium estándar para todas las ubicaciones enterprise", + "brand": "Panadería Central", + "unit_of_measure": "UNITS", + "package_size": null, + "average_cost": 1.80, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 100.0, + "reorder_point": 150.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": 1, + "display_life_hours": 24, + "best_before_hours": 12, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": true, + "allergen_info": [ + "gluten", + "may_contain_sesame" + ], + "nutritional_info": null, + "produced_locally": true, + "recipe_id": "30000000-0000-0000-0000-000000000001", + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "d2e3f4a5-b6c7-48d9-e0f1-a2b3c4d5e6f7", + "enterprise_shared": true, + "shared_locations": ["Madrid Centro", "Barcelona Gràcia", "Valencia Ruzafa"] + } + ], + "stock": [ + { + "id": "10000000-0000-0000-0000-000000001001", + "tenant_id": "80000000-0000-4000-a000-000000000001", + "ingredient_id": "10000000-0000-0000-0000-000000000001", + "quantity": 850.0, + "location": "Central Warehouse - Madrid", + "production_stage": "RAW_MATERIAL", + "quality_status": "APPROVED", + "expiration_date": "2025-07-15T00:00:00Z", + "supplier_id": "40000000-0000-0000-0000-000000000001", + "batch_number": "ENT-HAR-20250115-001", + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "enterprise_shared": true + }, + { + "id": "10000000-0000-0000-0000-000000001002", + "tenant_id": "80000000-0000-4000-a000-000000000001", + "ingredient_id": "10000000-0000-0000-0000-000000000002", + "quantity": 280.0, + "location": "Central Warehouse - Madrid", + "production_stage": "RAW_MATERIAL", + "quality_status": "APPROVED", + "expiration_date": "2025-02-15T00:00:00Z", + "supplier_id": "40000000-0000-0000-0000-000000000002", + "batch_number": "ENT-MAN-20250115-001", + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "enterprise_shared": true + }, + { + "id": "20000000-0000-0000-0000-000000001001", + "tenant_id": "80000000-0000-4000-a000-000000000001", + "ingredient_id": "20000000-0000-0000-0000-000000000001", + "quantity": 120.0, + "location": "Central Warehouse - Madrid", + "production_stage": "FINISHED_PRODUCT", + "quality_status": "APPROVED", + "expiration_date": "2025-01-16T06:00:00Z", + "supplier_id": null, + "batch_number": "ENT-BAG-20250115-001", + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "enterprise_shared": true + } + ] +} \ No newline at end of file diff --git a/shared/demo/fixtures/professional/03-inventory.json b/shared/demo/fixtures/professional/03-inventory.json new file mode 100644 index 00000000..32c5720c --- /dev/null +++ b/shared/demo/fixtures/professional/03-inventory.json @@ -0,0 +1,1020 @@ +{ + "ingredients": [ + { + "id": "10000000-0000-0000-0000-000000000001", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Harina de Trigo T55", + "sku": "HAR-T55-001", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "FLOUR", + "product_category": "BREAD", + "subcategory": null, + "description": "Harina de trigo refinada tipo 55, ideal para panes tradicionales y bollería", + "brand": "Molinos San José", + "unit_of_measure": "KILOGRAMS", + "package_size": null, + "average_cost": 0.85, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 100.0, + "reorder_point": 150.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": null, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": false, + "allergen_info": [ + "gluten" + ], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "10000000-0000-0000-0000-000000000002", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Harina de Trigo T65", + "sku": "HAR-T65-002", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "FLOUR", + "product_category": "BREAD", + "subcategory": null, + "description": "Harina de trigo semi-integral tipo 65, perfecta para panes rústicos", + "brand": "Molinos San José", + "unit_of_measure": "KILOGRAMS", + "package_size": null, + "average_cost": 0.95, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 80.0, + "reorder_point": 120.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": null, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": false, + "allergen_info": [ + "gluten" + ], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "10000000-0000-0000-0000-000000000003", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Harina de Fuerza W300", + "sku": "HAR-FUE-003", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "FLOUR", + "product_category": "BREAD", + "subcategory": null, + "description": "Harina de gran fuerza W300, ideal para masas con alta hidratación", + "brand": "Harinas Premium", + "unit_of_measure": "KILOGRAMS", + "package_size": null, + "average_cost": 1.15, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 50.0, + "reorder_point": 80.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": null, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": false, + "allergen_info": [ + "gluten" + ], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "10000000-0000-0000-0000-000000000004", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Harina Integral de Trigo", + "sku": "HAR-INT-004", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "FLOUR", + "product_category": "BREAD", + "subcategory": null, + "description": "Harina integral 100% con salvado, rica en fibra", + "brand": "Bio Cereales", + "unit_of_measure": "KILOGRAMS", + "package_size": null, + "average_cost": 1.2, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 60.0, + "reorder_point": 90.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": null, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": false, + "allergen_info": [ + "gluten" + ], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "10000000-0000-0000-0000-000000000005", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Harina de Centeno", + "sku": "HAR-CEN-005", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "FLOUR", + "product_category": "BREAD", + "subcategory": null, + "description": "Harina de centeno pura, para panes con sabor intenso", + "brand": "Harinas del Campo", + "unit_of_measure": "KILOGRAMS", + "package_size": null, + "average_cost": 1.3, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 40.0, + "reorder_point": 60.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": null, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": false, + "allergen_info": [ + "gluten" + ], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "10000000-0000-0000-0000-000000000006", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Harina de Espelta Ecológica", + "sku": "HAR-ESP-006", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "FLOUR", + "product_category": "BREAD", + "subcategory": null, + "description": "Harina de espelta certificada ecológica, de cultivo sostenible", + "brand": "Bio Cereales", + "unit_of_measure": "KILOGRAMS", + "package_size": null, + "average_cost": 2.45, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 30.0, + "reorder_point": 50.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": null, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": false, + "allergen_info": [ + "gluten" + ], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "10000000-0000-0000-0000-000000000011", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Mantequilla sin Sal 82% MG", + "sku": "LAC-MAN-001", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "DAIRY", + "product_category": "OTHER_PRODUCTS", + "subcategory": null, + "description": "Mantequilla de alta calidad 82% materia grasa, sin sal", + "brand": "Lácteos del Valle", + "unit_of_measure": "KILOGRAMS", + "package_size": null, + "average_cost": 6.5, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 20.0, + "reorder_point": 40.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": 90, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": true, + "allergen_info": [ + "lacteos" + ], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "10000000-0000-0000-0000-000000000012", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Leche Entera Fresca", + "sku": "LAC-LEC-002", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "DAIRY", + "product_category": "OTHER_PRODUCTS", + "subcategory": null, + "description": "Leche entera fresca pasteurizada 3.5% MG", + "brand": "Granja Santa Clara", + "unit_of_measure": "LITERS", + "package_size": null, + "average_cost": 0.95, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 50.0, + "reorder_point": 80.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": 7, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": true, + "allergen_info": [ + "lacteos" + ], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "10000000-0000-0000-0000-000000000013", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Nata para Montar 35% MG", + "sku": "LAC-NAT-003", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "DAIRY", + "product_category": "OTHER_PRODUCTS", + "subcategory": null, + "description": "Nata líquida para montar 35% materia grasa", + "brand": "Lácteos Premium", + "unit_of_measure": "LITERS", + "package_size": null, + "average_cost": 3.2, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 15.0, + "reorder_point": 30.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": 21, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": true, + "allergen_info": [ + "lacteos" + ], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "10000000-0000-0000-0000-000000000014", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Huevos Frescos Categoría A", + "sku": "LAC-HUE-004", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "DAIRY", + "product_category": "OTHER_PRODUCTS", + "subcategory": null, + "description": "Huevos frescos de gallinas camperas, categoría A", + "brand": "Granja Los Nogales", + "unit_of_measure": "UNITS", + "package_size": null, + "average_cost": 0.25, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 200.0, + "reorder_point": 300.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": 28, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": true, + "allergen_info": [ + "huevo" + ], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "10000000-0000-0000-0000-000000000021", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Levadura Fresca de Panadería", + "sku": "LEV-FRE-001", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "YEAST", + "product_category": "OTHER_PRODUCTS", + "subcategory": null, + "description": "Levadura fresca prensada de alta actividad", + "brand": "Lesaffre", + "unit_of_measure": "KILOGRAMS", + "package_size": null, + "average_cost": 4.8, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 5.0, + "reorder_point": 10.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": 45, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": true, + "allergen_info": [], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "10000000-0000-0000-0000-000000000022", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Levadura Seca Instantánea", + "sku": "LEV-SEC-002", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "YEAST", + "product_category": "OTHER_PRODUCTS", + "subcategory": null, + "description": "Levadura seca de rápida activación", + "brand": "Saf-Instant", + "unit_of_measure": "KILOGRAMS", + "package_size": null, + "average_cost": 12.5, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 3.0, + "reorder_point": 5.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": 730, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": false, + "allergen_info": [], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "10000000-0000-0000-0000-000000000023", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Masa Madre Líquida Natural", + "sku": "LEV-MAD-003", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "YEAST", + "product_category": "OTHER_PRODUCTS", + "subcategory": null, + "description": "Masa madre líquida artesanal de producción propia", + "brand": "Producción Propia", + "unit_of_measure": "KILOGRAMS", + "package_size": null, + "average_cost": 2.0, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 5.0, + "reorder_point": 8.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": 30, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": true, + "allergen_info": [ + "gluten" + ], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "10000000-0000-0000-0000-000000000031", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Sal Marina Fina", + "sku": "BAS-SAL-001", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "SALT", + "product_category": "OTHER_PRODUCTS", + "subcategory": null, + "description": "Sal marina fina para panadería", + "brand": "Sal del Mediterráneo", + "unit_of_measure": "KILOGRAMS", + "package_size": null, + "average_cost": 0.6, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 50.0, + "reorder_point": 80.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": null, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": false, + "allergen_info": [], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "10000000-0000-0000-0000-000000000032", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Azúcar Blanco Refinado", + "sku": "BAS-AZU-002", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "SUGAR", + "product_category": "OTHER_PRODUCTS", + "subcategory": null, + "description": "Azúcar blanco refinado de remolacha", + "brand": "Azucarera Española", + "unit_of_measure": "KILOGRAMS", + "package_size": null, + "average_cost": 0.9, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 80.0, + "reorder_point": 120.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": null, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": false, + "allergen_info": [], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "10000000-0000-0000-0000-000000000033", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Agua Filtrada", + "sku": "BAS-AGU-003", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "OTHER", + "product_category": "OTHER_PRODUCTS", + "subcategory": null, + "description": "Agua filtrada de calidad para panadería", + "brand": "Suministro Local", + "unit_of_measure": "LITERS", + "package_size": null, + "average_cost": 0.02, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 500.0, + "reorder_point": 800.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": null, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": false, + "allergen_info": [], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "10000000-0000-0000-0000-000000000041", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Chocolate Negro 70% Cacao", + "sku": "ESP-CHO-001", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "OTHER", + "product_category": "OTHER_PRODUCTS", + "subcategory": null, + "description": "Chocolate de cobertura negro 70% cacao", + "brand": "Valrhona", + "unit_of_measure": "KILOGRAMS", + "package_size": null, + "average_cost": 15.5, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 10.0, + "reorder_point": 20.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": 365, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": false, + "allergen_info": [ + "lacteos", + "soja" + ], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "10000000-0000-0000-0000-000000000042", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Almendras Laminadas", + "sku": "ESP-ALM-002", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "OTHER", + "product_category": "OTHER_PRODUCTS", + "subcategory": null, + "description": "Almendras españolas laminadas naturales", + "brand": "Frutos Secos Valencia", + "unit_of_measure": "KILOGRAMS", + "package_size": null, + "average_cost": 8.9, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 15.0, + "reorder_point": 25.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": 180, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": false, + "allergen_info": [ + "frutos_secos" + ], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "10000000-0000-0000-0000-000000000043", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Pasas de Corinto", + "sku": "ESP-PAS-003", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "OTHER", + "product_category": "OTHER_PRODUCTS", + "subcategory": null, + "description": "Pasas de Corinto sin semilla", + "brand": "Frutas del Sol", + "unit_of_measure": "KILOGRAMS", + "package_size": null, + "average_cost": 4.5, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 10.0, + "reorder_point": 20.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": 365, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": false, + "allergen_info": [], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "10000000-0000-0000-0000-000000000044", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Vainilla en Rama Madagascar", + "sku": "ESP-VAI-004", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "SPICES", + "product_category": "OTHER_PRODUCTS", + "subcategory": null, + "description": "Vainas de vainilla bourbon de Madagascar", + "brand": "Especias Premium", + "unit_of_measure": "UNITS", + "package_size": null, + "average_cost": 3.5, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 20.0, + "reorder_point": 40.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": 730, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": false, + "allergen_info": [], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "10000000-0000-0000-0000-000000000045", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Crema Pastelera en Polvo", + "sku": "ESP-CRE-005", + "barcode": null, + "product_type": "INGREDIENT", + "ingredient_category": "OTHER", + "product_category": "OTHER_PRODUCTS", + "subcategory": null, + "description": "Crema pastelera en polvo, fácil preparación", + "brand": "Sosa Ingredients", + "unit_of_measure": "KILOGRAMS", + "package_size": null, + "average_cost": 7.2, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": 5.0, + "reorder_point": 10.0, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": 540, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": false, + "allergen_info": [ + "lacteos", + "huevo" + ], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "20000000-0000-0000-0000-000000000001", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Baguette Tradicional", + "sku": "PRO-BAG-001", + "barcode": null, + "product_type": "FINISHED_PRODUCT", + "ingredient_category": "OTHER", + "product_category": "BREAD", + "subcategory": null, + "description": "Baguette francesa tradicional de 250g", + "brand": "Producción Propia", + "unit_of_measure": "UNITS", + "package_size": null, + "average_cost": 0.45, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": null, + "reorder_point": null, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": 1, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": true, + "allergen_info": [ + "gluten" + ], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "20000000-0000-0000-0000-000000000002", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Croissant de Mantequilla", + "sku": "PRO-CRO-001", + "barcode": null, + "product_type": "FINISHED_PRODUCT", + "ingredient_category": "OTHER", + "product_category": "CROISSANTS", + "subcategory": null, + "description": "Croissant artesanal de mantequilla 70g", + "brand": "Producción Propia", + "unit_of_measure": "UNITS", + "package_size": null, + "average_cost": 0.68, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": null, + "reorder_point": null, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": 2, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": true, + "allergen_info": [ + "gluten", + "lacteos" + ], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "20000000-0000-0000-0000-000000000003", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Pan de Pueblo", + "sku": "PRO-PUE-001", + "barcode": null, + "product_type": "FINISHED_PRODUCT", + "ingredient_category": "OTHER", + "product_category": "BREAD", + "subcategory": null, + "description": "Hogaza de pan de pueblo con masa madre 800g", + "brand": "Producción Propia", + "unit_of_measure": "UNITS", + "package_size": null, + "average_cost": 1.85, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": null, + "reorder_point": null, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": 5, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": true, + "allergen_info": [ + "gluten" + ], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + }, + { + "id": "20000000-0000-0000-0000-000000000004", + "tenant_id": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "name": "Napolitana de Chocolate", + "sku": "PRO-NAP-001", + "barcode": null, + "product_type": "FINISHED_PRODUCT", + "ingredient_category": "OTHER", + "product_category": "PASTRIES", + "subcategory": null, + "description": "Napolitana de hojaldre rellena de chocolate 90g", + "brand": "Producción Propia", + "unit_of_measure": "UNITS", + "package_size": null, + "average_cost": 0.72, + "last_purchase_price": null, + "standard_cost": null, + "low_stock_threshold": null, + "reorder_point": null, + "reorder_quantity": null, + "max_stock_level": null, + "shelf_life_days": 2, + "display_life_hours": null, + "best_before_hours": null, + "storage_instructions": null, + "central_baker_product_code": null, + "delivery_days": null, + "minimum_order_quantity": null, + "pack_size": null, + "is_active": true, + "is_perishable": true, + "allergen_info": [ + "gluten", + "lacteos", + "soja" + ], + "nutritional_info": null, + "produced_locally": false, + "recipe_id": null, + "created_at": "2025-01-15T06:00:00Z", + "updated_at": "2025-01-15T06:00:00Z", + "created_by": "c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6" + } + ] +} \ No newline at end of file