You may have started with a nice abstraction for one case, but you don't have time to reabstract every step of the way.
Mediocre/poor developers just think about what needs to get done. They will copy code that seems to do something similar and hack on it until it works.
I saw this a lot with asp pages back in the day. An entire application, that actually worked and was well liked by its users, was implemented as hundreds of totally stand-alone .asp files. When a new page was needed, the developer copied an old one and modified it. That made it pretty easy to add new features without breaking old ones, but made it very difficult to make cross-cutting changes such as changing the name or type of a field on multiple pages, changing page headers, changing database connections (yes, they were also duplicated in every file) etc.
This is also how you end up with a "20k line VB.Net disaster." They are created by developers who know just enough to make something work, but don't know about abstraction and modularization or aren't smart enough or experienced enough to see the abstractions and to keep track of what's going on when the code is split out over a dozen files or modules. Or possibly, just don't care.
I totally get the low job security and low payment and the "I don't care" parts and have written bad code because of all of those. Still, if you put even a minimal effort, it pays off.
Experience is obviously good but people tend to get inflexible and dogmatic, too.
When you're eventually wrong, someone must pay the incredibly expensive price of deabstraction. Which can become so untenable that it makes more sense to escape-latch out of it for a new business requirement. If you disagree, then shrink the deadline until you do.
What happens with technical debt is that every new/changed feature incurs disproportional costs. And all costs, at the end of the day, boil down into time. Even the best developer has the same finite resource of time as everyone else, and they are stuck choosing between the best of suboptimal solutions once bounded by deadlines.
This is why, when encountering a mess of a codebase, it's naive to conclude "wow, what a bunch of amateurs." And that's exactly what I thought at my first job out of university. Eventually I realized that software is just hard and there is never enough time. The more experienced you get, the better you are at writing code that can be changed or thrown away. But you're still only minimizing the bad, not eliminating the bad, so on a long enough time scale with enough monkey wrenches of time constraints and requirement churn, technical debt is inevitable.
Having something like that is like a pressure point for business risk. A small nudge here, and the whole shaky house of cards violently implodes ejecting all kinds of badness, monetary and otherwise, over those around it.
And it is not just the bad component, it is the overall system design which permits this and does not support change within the system ("modifiability", "extensibility", ...).
Point: it is not as easy as saying "oh, this is because of that incompetent asshat". The incompetent asshattery is a systemic phenomenon with many actors. Death by a thousand small bad choices; the 20 LOC of untouchable code is just a manifestation of the bigger problems.