createSchema(); DB::table('tenants')->insert(['codigo' => 'mutual_smep']); $this->seedCategoriesAndAttributes(); } protected function tearDown(): void { foreach ([ 'featured_items', 'featured_groups', 'catalog_items_attachments', 'attachments', 'variant_values', 'item_attributes', 'variantes', 'catalog_items', 'inventories', 'brands', 'attribute', 'categorias', 'tenants', ] as $table) { Schema::dropIfExists($table); } parent::tearDown(); } public function test_it_loads_and_rolls_back_the_complete_catalog(): void { $migration = require database_path( 'migrations/2026_09_24_030000_seed_mutual_smep_catalog.php' ); $migration->up(); $this->assertSame(142, DB::table('catalog_items')->count()); $this->assertSame(150, DB::table('inventories')->count()); $this->assertSame(15, DB::table('variantes')->count()); $this->assertSame(8, DB::table('item_attributes')->count()); $this->assertSame(17, DB::table('variant_values')->count()); $this->assertSame(511, DB::table('attachments')->count()); $this->assertSame(511, DB::table('catalog_items_attachments')->count()); $this->assertSame( 511, count(Storage::disk('s3')->allFiles('tenants/mutual_smep/catalog-v1')), ); $this->assertDatabaseCount('featured_groups', 7); $this->assertSame([ 'Productos destacados', 'Dormitorio y Blanco', 'Electrodomésticos', 'Climatización', 'Tecnología', 'Bicicletas y Aire Libre', 'Bazar', ], DB::table('featured_groups')->orderBy('group_order')->pluck('group_name')->all()); DB::table('featured_groups')->orderBy('group_order')->each(function (object $group): void { $this->assertSame('manual', $group->source_type); $this->assertSame('carousel', $group->group_layout); $this->assertSame('column_with_image', $group->product_layout); if ($group->code === 'productos-destacados-smep') { $this->assertNull($group->category_id); } else { $this->assertNotNull($group->category_id); $this->assertDatabaseHas('categorias', [ 'id' => $group->category_id, 'tenant_code' => 'mutual_smep', 'categoria_id' => null, 'nombre' => $group->group_name, ]); } $this->assertGreaterThan( 0, DB::table('featured_items')->where('featured_group_id', $group->id)->count(), ); }); $highlightedGroupId = DB::table('featured_groups') ->where('code', 'productos-destacados-smep') ->value('id'); $highlightedSlugs = DB::table('featured_items') ->join('catalog_items', 'catalog_items.id', '=', 'featured_items.catalog_item_id') ->where('featured_items.featured_group_id', $highlightedGroupId) ->orderBy('featured_items.order') ->pluck('catalog_items.slug') ->all(); $variantProductSlugs = DB::table('catalog_items') ->whereExists(fn ($query) => $query ->selectRaw('1') ->from('variantes') ->whereColumn('variantes.catalog_item_id', 'catalog_items.id')) ->pluck('slug') ->all(); $this->assertCount(13, $highlightedSlugs); $this->assertSame([], array_values(array_diff($variantProductSlugs, $highlightedSlugs))); $this->assertSame(155, DB::table('featured_items')->count()); $this->assertDatabaseHas('catalog_items', [ 'tenant_code' => 'mutual_smep', 'slug' => 'motorola-g15', 'precio' => 330000, ]); $this->assertSame(10, DB::table('inventories')->min('real_stock')); $this->assertSame(10, DB::table('inventories')->max('real_stock')); $migration->down(); foreach ([ 'featured_items', 'featured_groups', 'catalog_items_attachments', 'attachments', 'variant_values', 'item_attributes', 'variantes', 'catalog_items', 'inventories', 'brands', ] as $table) { $this->assertSame(0, DB::table($table)->count(), $table); } $this->assertSame([], Storage::disk('s3')->allFiles('tenants/mutual_smep/catalog-v1')); } private function seedCategoriesAndAttributes(): void { $manifest = json_decode( (string) file_get_contents(database_path('data/mutual_smep_catalog_v1.json')), true, flags: JSON_THROW_ON_ERROR, ); $parents = []; foreach ($manifest['items'] as $item) { $parentName = $item['parent_category']; $parentId = $parents[$parentName] ??= DB::table('categorias')->insertGetId([ 'tenant_code' => 'mutual_smep', 'categoria_id' => null, 'nombre' => $parentName, ]); DB::table('categorias')->insertOrIgnore([ 'tenant_code' => 'mutual_smep', 'categoria_id' => $parentId, 'nombre' => $item['category'], ]); } foreach (['color', 'medida_cama', 'almacenamiento', 'memoria_ram', 'rodado'] as $code) { DB::table('attribute')->insert([ 'tenant_codigo' => 'mutual_smep', 'codigo' => $code, 'nombre' => $code, ]); } } private function createSchema(): void { Schema::create('tenants', fn (Blueprint $table) => $table->string('codigo')->primary()); Schema::create('categorias', function (Blueprint $table): void { $table->id(); $table->string('tenant_code'); $table->unsignedBigInteger('categoria_id')->nullable(); $table->string('nombre'); $table->unique(['tenant_code', 'categoria_id', 'nombre']); }); Schema::create('attribute', function (Blueprint $table): void { $table->id(); $table->string('tenant_codigo'); $table->string('codigo'); $table->string('nombre'); }); Schema::create('brands', function (Blueprint $table): void { $table->id(); $table->string('tenant_codigo'); $table->string('nombre'); $table->text('descripcion')->nullable(); $table->timestamps(); }); Schema::create('inventories', function (Blueprint $table): void { $table->id(); $table->unsignedBigInteger('sold_units')->default(0); $table->unsignedBigInteger('refunded_units')->default(0); $table->unsignedInteger('reserved_stock')->default(0); $table->unsignedInteger('real_stock')->default(0); }); Schema::create('catalog_items', function (Blueprint $table): void { $table->id(); $table->string('tenant_code'); $table->unsignedBigInteger('category_id')->nullable(); $table->unsignedBigInteger('brand_id')->nullable(); $table->unsignedBigInteger('inventory_id')->nullable(); $table->string('type'); $table->string('slug'); $table->string('nombre'); $table->unsignedInteger('group_order')->default(0); $table->text('descripcion')->nullable(); $table->decimal('precio', 10, 2); $table->string('inventory_policy'); $table->string('inventory_subject'); $table->boolean('has_tickets')->default(false); $table->unique(['tenant_code', 'slug']); }); Schema::create('variantes', function (Blueprint $table): void { $table->id(); $table->foreignId('catalog_item_id')->constrained('catalog_items')->cascadeOnDelete(); $table->unsignedBigInteger('inventory_id'); $table->text('descripcion')->nullable(); $table->decimal('precio', 10, 2)->nullable(); }); Schema::create('item_attributes', function (Blueprint $table): void { $table->id(); $table->foreignId('catalog_item_id')->constrained('catalog_items')->cascadeOnDelete(); $table->unsignedBigInteger('attribute_id'); $table->boolean('allow_multi_select')->default(false); $table->unsignedInteger('sort_order')->default(0); $table->boolean('show_in_selector')->default(true); $table->string('ticket_label')->nullable(); $table->timestamps(); }); Schema::create('variant_values', function (Blueprint $table): void { $table->id(); $table->foreignId('variant_id')->constrained('variantes')->cascadeOnDelete(); $table->foreignId('item_attribute_id')->constrained('item_attributes')->cascadeOnDelete(); $table->text('value')->nullable(); $table->timestamps(); }); Schema::create('attachments', function (Blueprint $table): void { $table->id(); $table->uuid('key')->unique(); $table->string('path'); $table->string('filename'); $table->string('type'); $table->string('mime_type'); $table->string('extension')->nullable(); $table->unsignedBigInteger('size')->default(0); $table->timestamps(); }); Schema::create('catalog_items_attachments', function (Blueprint $table): void { $table->id(); $table->foreignId('variant_id')->nullable()->constrained('variantes')->cascadeOnDelete(); $table->foreignId('catalog_item_id')->constrained('catalog_items')->cascadeOnDelete(); $table->foreignId('attachment_id')->constrained('attachments')->cascadeOnDelete(); $table->unsignedInteger('orden')->default(0); $table->boolean('is_enabled')->default(true); }); Schema::create('featured_groups', function (Blueprint $table): void { $table->id(); $table->string('tenant_code'); $table->string('code'); $table->string('source_type'); $table->unsignedBigInteger('category_id')->nullable(); $table->string('product_layout'); $table->string('group_layout'); $table->string('group_name'); $table->unsignedInteger('group_order')->default(0); }); Schema::create('featured_items', function (Blueprint $table): void { $table->id(); $table->foreignId('featured_group_id')->constrained('featured_groups')->cascadeOnDelete(); $table->foreignId('catalog_item_id')->constrained('catalog_items')->cascadeOnDelete(); $table->unsignedInteger('order')->default(0); }); } }