But in that case how would have the code worked? Still confused about this.
<on soap-box>
There is a big problem with engineers when they say something "works", not just in hardware engineering, but in all aspects of engineering. True verification requires an understanding of the range of possible situations and their limits.
You only created one simple testcase where PARAM_A_PRESENT was set to 1, and only one instance of the dummy module. But the idea you are trying to explore is reusing the same code for different varying situations.
How could you have possible tested this theory with only one variation?
Had you tried `define A_PRESENT 0 in your testbench, or had two instances of dummy with different parameter overrides, you would have discovered that the `define A_PRESENT inside the generate happens unconditionally regardless of what the generate condition evaluates to.
<off soap-box>
The reason for this is that Verilog macros are pre-processed and expanded into text before any other Verilog syntax parsing. You can effectively think of macro processing as a separate tool that executes before being fed into the Verilog compiler. That means there is only one text declaration of a module. Then the Verilog parsing begins. And there is another step called elaboration. This is where the parameter overrides of each instance create unique definitions of module executing the generate blocks within them. Verilog has severe limits on what can be parameterized on an instance by instance basis and, unfortunately, changing the number of ports is one of those restrictions.
What you can do is make a port an array and change the size of the array as a parameter. This can work if the ports you want to add are all the same type. SystemVerilog adds the ability to parameterize types, which can be structures, and also has an interface construct that effectively puts the port declarations into a separate entity.