ansible: difference between a variable and a fact
Ansible facts are data collected about the (target) systems on which Ansible takes actions. They are variables, but set by Ansible (in a way like system defined variables). They are collected during Gathering Facts stage of a playbook run, and it is controlled by the gather_facts setting. Ansible calls this variables discovered from systems. That said, it is possible to set custom facts also.
Some examples are:

ansible_hostname - the FQDN of the target system
ansible_os_family - the Operating System family of target system (RedHat, Debian, etc.)

The other variables are the ones we can set as per our requirement (in a way like user defined variables).
Some examples are:

my_fav_fruits: [ 'orange', 'apple', 'banana' ] - yours might differ.
config_dir: '/etc/my_app/conf.d' - for my application configuration files.


Update:
Updating answer to make it relevant to the edit(s) made in question.
As @Bruce Becker's answer rightly pointed out, there is a difference in the precedence of variables set with set_fact. Also variables that need to be set at &quot;run time&quot; can be set this way. Without further explanation, taking your example variables, if I create a play like below:
- hosts: localhost
  vars:
    nginx_ssl: '/etc/nginx/ssl'
    nginx_conf_file: '/etc/nginx/nginx.conf'

  tasks:
  - name: set nginx path to /opt when running on Debian
    set_fact:
      nginx_ssl: '/opt/nginx/ssl'
      nginx_conf_file: '/opt/nginx/nginx.conf'
    when: ansible_distribution == 'Debian'
  - debug:
      msg: 'ssl: {{ nginx_ssl }} and conf: {{ nginx_conf_file }}'

Then the set_fact variables will take precedence (on Debian) and output will be:
&quot;msg&quot;: &quot;ssl: /opt/nginx/ssl and conf: /opt/nginx/nginx.conf&quot;

On other distributions, it will be what was declared in vars:.