Right there you've stated a key practice that may, by itself, be 80% of the solution. Of course there's no "right" set of of types, but just putting the work into thinking about the types and the essential constraints will drive towards exposing and eliminating assumptions that will come back to bite later.
Another huge chunk of problems can come from over-reliance on language primitive types. Is that numeric field really an integer, meaning that any signed value representable in 32, 64, or however many bits, including zero, is valid? Probably not. To give an example: A person's age is not an signed integer, it's a duration, we don't expect it to be negative. Maybe we should flag values above 90 or 100. If you're doing a one-off, that's the sort of thing you can code once and forget. If you dataset migration is your business, your Age type needs to be more robust, and perhaps there's even a need for the behavior of Age to vary with the domain.
int i = -2;
Console.WriteLine((Age)i);
Console.WriteLine((Age)200);
Console.WriteLine((AdultAge<UnitedStates>)17);
will throw three Exceptions. We define every single piece of data like that.