Magento 2.4.7 store, 15k products: product pages rank, category pages stuck at pos. 18-25 for two years

I run a Spanish baby/children’s products store on Magento 2.4.7 (Porto theme, Cloudflare in front, ~15,000 products, ~420 commercial categories, domain since 2010). I have spent nine months and dozens of read-only forensic audits on this, and I’m posting because I’ve run out of hypotheses, not because I haven’t looked. I’ll keep it factual; happy to share any log or export.

The pattern

Product pages rank normally. For a core query like “moisés bebé” (baby bassinet), individual product pages sit at pos. 6-8.
The category page for the same query sits at pos. 22-27, and has for two years. It is the page with by far the most impressions for that cluster (64% of impressions for the head term, 86% of the cluster), so it’s not fragmentation between URLs.
Across all 424 commercial categories the median position is ~18-20. The store enters the top 5 only where there’s essentially no competition.
The pages outranking the category are tiny: a handful of products, supplier-copied descriptions, some visibly abandoned (no updates in years, broken blogs). They have no measurable backlink profile. My category has a long, hand-written buying guide, 240 products, proper internal linking. Content volume is not the difference.
Sistrix visibility: peak ~1.1 (Mar 2023), single step down at the Oct 2023 core update (~0.65 → ~0.25), partial recovery to ~0.6 in H1 2024, then continuous bleed to 0.12 today. GSC impressions: 661k (Dec 2024) → 354k (Mar 2025) → 123k (Sep 2026). Clicks flat at ~40-50/day for 9 months. No manual action, no security issues in GSC.
Google Images used to be the main traffic source (30-40 clicks/day); it fell to 3-4/day at the Aug 2025 migration and never came back, even after all image URLs were fixed and re-crawled.

What has been checked and ruled out (each with logs or exports, not guesses)

Crawl access

Cloudflare: 30 days of request logs filtered on ASN 15169 show 0 mitigated requests. Bot Fight Mode permissive, Hotlink Protection off, Browser Integrity Check off, Rocket Loader off, no HTML caching at the edge, SSL Full (strict). A country-block rule existed, but Google was exempted by a skip rule placed above it since June; verified 814k events, zero blocks or challenges to Google. Pausing the country rule for a week changed nothing for Googlebot.
Server: zero 5xx to verified Googlebot. 0 blocks. Googlebot reaches the origin at a steady ~5-7k requests/day; it’s not being throttled, it just doesn’t ask for more.

Rendering / delivery

Fetched the category as Googlebot, as Chrome, and from origin: identical HTML, no JS dependency for content, LCP mobile ~3.5s. Compared against a control category on the same theme that ranks pos. 5-9: same template, same weight, same script count. So the theme’s weight (large mega-menu, ~200 script requests) is shared by categories that rank fine.

Index hygiene

Faceted navigation: it was open to crawl from roughly Oct 2024 to Sep 2026 (a Mageplaza layered-nav multi-select bug generated >10⁶ URLs; Googlebot crawled 142k filter URLs in one week in Aug 2026). Fixed: noindex since Mar 2026, full robots block since Sep 2026. GSC now shows 15k indexed, 881k “blocked by robots”, 144k “noindex” — all expected and shrinking. No facet URL is indexed.
Pagination: self-canonical on all paginated pages since Jul 2026. Out-of-range ?p=N pages fixed.
A noindex bug (theme robots plugin) hit Feb-Apr 2026; fixed Apr 13. Visibility didn’t come back afterwards.
Images: a legacy /cache/ image URL pattern was replaced by a stable path for all 12.7k products in Jul 2026; Googlebot-Image has been re-crawling RAW → stable 301s since.

Site history

An old SEO-spam snippet (serp.trade iframe injected in two category descriptions) was present 2019 → mid-2025, removed at the migration. No manual action ever. Note the store ranked at its best (2023) with that snippet present, so it’s unlikely to be the ceiling.
Robots.txt was emptied by mistake for ~6 months in 2024-25 (filters crawlable), then restored.
Apex domain served the home page as 200 without canonical for ~1 year (2024-25); now 301 to www.

Things that did move the needle

Adding 183 products to one category in Jan 2026 moved it from pos. 35 to 19. Nothing else, technical or editorial, has ever moved a category position in two years.

What I’m asking

I am not asking for a generic audit checklist; everything on the usual lists has been done and verified with data. What I’m looking for is someone who has seen this specific shape before:

A Magento 2 store where category pages are systematically capped ~15 positions below its own product pages, with the technical layer verified clean, and what the cause turned out to be.
Whether a two-year history of index inflation (10⁶ facet URLs known to Google, now blocked) is known to leave a lasting “ceiling” on category pages even after cleanup, and how long recovery took in cases you’ve seen.
Whether anyone has seen a Google Images collapse tied to a Magento 2 migration that did not recover after fixing the image URLs.

I can provide: GSC exports per URL, Googlebot log analysis (verified by rDNS), Cloudflare logs, the full forensic reports. Thank you for reading this far.