Saltar a contenido

📚 Unidad ISS-01 · Settings y selección del motor — capa 🧠 APRENDER

🛠 Construir este bloque → · ✅ Condiciones de cierre (GATE) · 📝 Evaluación

Capa Página Para qué
🧠 Aprender esta página comprender, explicar y relacionar
🛠 Construir Settings y selección del motor ejecutar, programar y verificar
✅ GATE Cierre de la unidad condición para pasar al bloque siguiente

Mapa de correspondencias. Cada fila enlaza el mismo tema en las dos capas; los enlaces apuntan a secciones reales del material.

Tema 🧠 Aprender (esta página) 🛠 Construir (ISS técnico)
Objetivo 1.0 Objetivos, Requisitos y Contexto Operativo Objetivo
Recorrido 4.0 Arquitectura de Configuración y Módulos Creados Construcción
Cierre 8.0 Matriz de Aceptación, Evidencias y Tabla GATE Criterios de aceptación · GATE
Evaluación 9.0 Cuestionario de Defensa Oral y Evaluación Técnica GATE

🖥 Presentación del ISS

Presentación de la unidad. Diapositivas de ISS-01 — Settings y selección del motor (19 diapositivas). Se visualiza aquí, dentro del sitio.

⛶ Ver presentación completa ⬇ Archivo editable (.pptx)

19 diapositivas · se visualiza dentro del sitio.

🎬 Video explicativo

Recorrido audiovisual de la unidad. El video recorre el ISS técnico de ISS-01 — Settings y selección del motor, bloque por bloque.

14:20 min · narración en español · subtítulos activables desde el reproductor.

Infografía

Infografía de la unidad

Mapa conceptual

  • Objetivo y Requisitos
  • Objetivo: Elegir un solo motor por proceso desde .env sin SQL
  • Requisito: ISS-00 superado y terminal con .venv activo
  • Requisito: Variables separadas para PostgreSQL, MySQL, SQL Server y Oracle
  • Instalación de Paquetes
  • python-dotenv==1.2.3: Lectura de .env
  • psycopg[binary]==3.3.6: Driver para PostgreSQL
  • mysqlclient==2.3.0: Driver para MySQL
  • mssql-django==2.0.0: Driver para SQL Server
  • oracledb==26.0.1: Driver para Oracle
  • requirements.txt: Actualizado con python -m pip freeze
  • Plantilla de Entorno (.env.example)
  • Configuración: DJANGO_SECRET_KEY y DJANGO_DEBUG=True
  • Configuración: ALLOWED_HOSTS con localhost y 127.0.0.1
  • Selector: DB_ENGINE con postgresql por defecto
  • Variables por motor: Credenciales y puertos específicos
  • CORS: CORS_ALLOW_ALL_ORIGINS=True para laboratorio académico
  • Traducción de Motor (config/database.py)
  • ALLOWED_ENGINES: postgresql, mysql, mssql y oracle
  • Validación: ImproperlyConfigured ante motor o variable inválida
  • _postgresql: Retorna django.db.backends.postgresql
  • _mysql: Retorna django.db.backends.mysql con charset utf8mb4
  • _mssql: Retorna mssql con ODBC Driver 18 y TrustServerCertificate
  • _oracle: Retorna django.db.backends.oracle con DSN y SERVICE_NAME
  • Paquete de Settings (config/settings/)
  • Sustitución: Borrar settings.py y crear directorio init.py
  • Carga: load_dotenv(BASE_DIR / '.env') antes de SECRET_KEY
  • INSTALLED_APPS: Apps base de Django (admin, auth, sessions, etc.)
  • DATABASES: Asignación con database_from_environ(os.environ)
  • Zona e Idioma: LANGUAGE_CODE: es y TIME_ZONE: America/Bogota
  • Intérprete y Parches de Arranque
  • config/project_python.py: use_project_python para reejecutar con .venv
  • manage.py: Inserción de sys.path y llamada a use_project_python
  • wsgi.py y asgi.py: Inserción de load_dotenv antes de aplicación
  • Criterios de Aceptación y Verificación
  • AC-01-01: Motores producen el ENGINE documentado
  • AC-01-02: Motor desconocido falla con ImproperlyConfigured
  • AC-01-03: Falta de variable obligatoria falla antes de conectar
  • AC-01-04: DSN de Oracle contiene SERVICE_NAME
  • Verificación: DatabaseSelectionTests con SimpleTestCase
  • Evidencia EVI-01-01: DatabaseSelectionTests PASS

Guía de Estudio Exhaustiva: ISS-01 — Configuración de Motor de Base de Datos y Entorno Multidialecto en Django


1.0 Objetivos, Requisitos y Contexto Operativo

1.1 Contextualización y Propósito del ISS-01

En la evolución del proyecto StoreLab, la unidad ISS-01 representa un hito infraestructural crítico. Su propósito estratégico es transformar la configuración por defecto monolítica generada por el comando startproject de Django —originalmente limitada a una base de datos local SQLite en un único archivo— en una arquitectura de configuración dinámica y desacoplada, guiada exclusivamente por variables de entorno y capaz de alternar entre cuatro motores relacionales de clase empresarial (PostgreSQL, MySQL, SQL Server y Oracle) por proceso de ejecución. En este punto de la construcción del sistema no existen aún modelos de negocio ni tablas de aplicación declaradas; el foco absoluto radica en establecer un núcleo de configuración sólido, dinámico y resiliente que servirá de cimiento para todos los incrementos posteriores del proyecto.

Para abordar con éxito la implementación del ISS-01, es imprescindible haber superado de manera rigurosa los requisitos del ISS-00. Específicamente, el sistema debe partir de una estructura limpia donde el entorno virtual aislado .venv haya sido creado con Python 3.12 y Django 5.2.17. Antes de ejecutar cualquier instalación de paquetes adicionales o reestructuración de archivos, es un requisito estricto y no negociable verificar que la sesión de la terminal se encuentre ejecutándose dentro del entorno virtual mediante los comandos source .venv/bin/activate y which python. Esta comprobación garantiza que la ruta del ejecutable apunte inequívocamente a .venv/bin/python, evitando la contaminación del intérprete de Python del sistema operativo global.

  • Punto de partida validado (ISS-00): Existencia de la carpeta .venv/, el script manage.py en la raíz y el paquete inicial config/ generado por la CLI de Django.
  • Entorno virtual activo: El prompt de la terminal debe mostrar explícitamente el prefijo (.venv).
  • Confirmación de intérprete: La ejecución de which python debe retornar la ruta absoluta hacia .venv/bin/python.
  • Aislamiento de paquetes: Garantía de que cualquier invocación a pip se realice mediante python -m pip dentro del entorno activo.
  • Ausencia de modelos: Confirmación de que el árbol del proyecto no contiene aún modelos de negocio ni migraciones aplicadas.

Una vez validadas las condiciones previas del entorno operativo, resulta indispensable dominar el marco conceptual y el vocabulario técnico que hacen posible el desacoplamiento entre el código fuente y el motor de base de datos subyacente.


2.0 Vocabulario Técnico y Conceptos Fundamentales

2.1 Marco Conceptual del Proyecto Multidialecto

El diseño de un sistema capaz de operar sobre múltiples dialectos de base de datos sin modificar una sola línea del código de aplicación exige la asimilación precisa de términos y mecanismos de bajo nivel. El dominio de este vocabulario técnico es indispensable para comprender cómo Django abstrae la infraestructura mediante controladores, variables de proceso y llamadas al sistema.

Término Definición Técnica Propósito en el Sistema
DB_ENGINE Variable de control de entorno utilizada para seleccionar de forma dinámica el motor de base de datos activo por cada proceso. Permite conmutar en tiempo de ejecución entre postgresql, mysql, mssql y oracle sin alterar archivos de código.
DSN (Data Source Name) Cadena de conexión estructurada que describe los parámetros de red, protocolo e instancia requeridos por motores empresariales. Formatear explícitamente la dirección, puerto y SERVICE_NAME en conexiones hacia bases de datos Oracle.
ImproperlyConfigured Excepción nativa del núcleo de Django (django.core.exceptions) lanzada de forma temprana ante inconsistencias de entorno. Interrumpir inmediatamente la ejecución si falta una variable obligatoria o si se especifica un motor no soportado.
load_dotenv Función provista por la librería python-dotenv que lee pares clave-valor desde un archivo .env y los inyecta en el entorno. Cargar la configuración local en el diccionario os.environ antes de que Django inicialice el paquete de settings.
sys.prefix Atributo de la biblioteca estándar de Python que contiene la ruta absoluta del directorio raíz del intérprete en ejecución. Comparar si la instancia actual de Python corre dentro del directorio .venv o en el intérprete del sistema operativo.
os.execv Invocación al sistema que reemplaza la imagen de memoria del proceso actual por un nuevo ejecutable con sus argumentos. Re-ejecutar de forma transparente el comando manage.py utilizando el binario .venv/bin/python si se arrancó por error con el Python global.

Comprendido el marco conceptual y los términos que gobiernan el comportamiento del sistema, el siguiente paso práctico consiste en la gestión e instalación de las dependencias y controladores necesarios a nivel de línea de comandos.


3.0 Gestión de Dependencias y Drivers CLI

3.1 Instalación y Análisis de Conectores de Base de Datos

Django incluye internamente la capa ORM para abstraer el lenguaje SQL de los motores soportados, pero no empaqueta los controladores (drivers) de bajo nivel necesarios para establecer la comunicación por red o interfaz C con los servidores de base de datos. Por esta razón, cada conector debe instalarse de forma explícita en el entorno virtual. Si se omite la instalación de alguno de estos paquetes, cualquier intento de ejecutar migraciones u operaciones de lectura fallará inmediatamente con errores de importación (ImportError) al cargar el backend correspondiente, incluso si el código de configuración en database.py está correctamente escrito.

Para asegurar la repetibilidad del entorno, se debe ejecutar el siguiente bloque de comandos CLI en la terminal con el entorno virtual previamente activado:

source .venv/bin/activate
which python
python -m pip install "python-dotenv==1.2.3"
python -m pip install "psycopg[binary]==3.3.6"
python -m pip install "mysqlclient==2.3.0"
python -m pip install "mssql-django==2.0.0"
python -m pip install "oracledb==26.0.1"
python -m pip freeze > requirements.txt

Desglose Analítico de las Librerías Instaladas

  • python-dotenv==1.2.3: Expone la función load_dotenv. Su función principal es leer el archivo .env del disco e inyectar sus valores en os.environ. Módulos clave de entrada como manage.py, wsgi.py y asgi.py lo importan de manera prioritaria antes de invocar la carga de los settings. Si esta librería falta, el proceso no logra inicializar las variables críticas del sistema.
  • psycopg[binary]==3.3.6: Es el controlador oficial de C precompilado que utiliza el backend django.db.backends.postgresql. El extra [binary] incluye las librerías dinámicas de C compiladas, evitando la necesidad de disponer de herramientas de compilación o cabeceras de desarrollo de C en el sistema operativo cliente.
  • mysqlclient==2.3.0: Es el controlador nativo en C documentado y recomendado oficialmente por Django para el backend django.db.backends.mysql. Proporciona la interfaz de alto rendimiento entre la capa de abstracción de Django y el servidor MySQL o MariaDB.
  • mssql-django==2.0.0: Adaptador especializado para Microsoft SQL Server. Registra el nombre de motor mssql dentro del ecosistema de Django y arrastra como dependencia interna a pyodbc.
  • oracledb==26.0.1: Es el controlador moderno de Python para Oracle Database que sustituye de forma oficial a cx_Oracle (deprecado a partir de Django 5.0). Sirve de soporte para el backend django.db.backends.oracle. Se instala en el entorno para garantizar la compatibilidad multidialecto completa del proyecto.

┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ │ DISTINCIÓN ARQUITECTÓNICA DE CONTROLADORES: BINARIOS DE C VS. ADAPTADORES DEL SISTEMA OPERATIVO │ ├──────────────────────────────────────────────────────────────────────────────────────────────────┤ │ Es crucial diferenciar dos categorías de conectores instalados en el entorno: │ │ │ │ 1. Controladores Binarios / C Puros (psycopg[binary], mysqlclient, oracledb): │ │ Empaquetan sus propias extensiones o interfaces C dentro del propio wheel de Python. Su │ │ instalación queda completamente contenida dentro del directorio .venv/ sin requerir │ │ librerías del sistema operativo adicionales. │ │ │ │ 2. Adaptadores Capa sobre Drivers del SO (mssql-django / pyodbc): │ │ mssql-django es un wrapper o adaptador Python que depende internamente de pyodbc. │ │ Atención: Este paquete Python NO instala el controlador nativo de red. Requiere de forma │ │ estricta que el sistema operativo anfitrión tenga instalado el paquete nativo │ │ ODBC Driver 18 for SQL Server. Si este driver del SO falta, Python fallará al conectar. │ └──────────────────────────────────────────────────────────────────────────────────────────────────┘

Con todos los controladores y librerías instalados y registrados en requirements.txt, se procede a la reestructuración física de los archivos de configuración del proyecto.


4.0 Arquitectura de Configuración y Módulos Creados

4.1 Diseño Estructural: De Archivo a Paquete de Settings

El comando django-admin startproject config . ejecutado en el ISS-00 genera por defecto un archivo monolítico en la ruta config/settings.py. Dado que en Python un archivo y una carpeta no pueden compartir el mismo nombre en el mismo nivel de espacio de nombres, el primer paso arquitectónico del ISS-01 consiste en eliminar dicho archivo e instituir un paquete de Python llamado config/settings/ que contendrá la lógica dividida, complementado por un módulo auxiliar config/database.py dedicado en exclusiva a la resolución del motor de base de datos.

Transformación de la Estructura de Archivos

ESTRUCTURA ANTES (ISS-00):
config/
├── __init__.py
├── asgi.py
├── settings.py      <-- Archivo monolítico generado por startproject
├── urls.py
└── wsgi.py

ESTRUCTURA DESPUÉS (ISS-01):
config/
├── __init__.py
├── asgi.py
├── database.py      <-- NUEVO: Módulo de selección dinámica de motor
├── project_python.py<-- NUEVO: Mecanismo de re-ejecución de intérprete
├── settings/
│   └── __init__.py  <-- NUEVO: Paquete de configuración modularizado
├── urls.py
└── wsgi.py
.env.example          <-- NUEVO: Plantilla no versionada de variables
.env                  <-- NUEVO: Archivo local con secretos de entorno

Gestión del Entorno: .env.example y .env

El archivo .env.example actúa como una plantilla pública de documentación. No contiene contraseñas ni secretos reales, únicamente las claves de las variables y sus valores por defecto seguros.

cat > .env.example <<'EOF'
# Copie este archivo a .env y complete los valores.
# .env no se versiona.

DJANGO_SECRET_KEY=
DJANGO_DEBUG=True
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,testserver

# Motor activo. Uno solo por ejecución: postgresql | mysql | mssql | oracle
DB_ENGINE=postgresql

POSTGRES_DB=
POSTGRES_USER=
POSTGRES_PASSWORD=
POSTGRES_HOST=127.0.0.1
POSTGRES_PORT=5432

MYSQL_DB=
MYSQL_USER=
MYSQL_PASSWORD=
MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306

MSSQL_DB=
MSSQL_USER=
MSSQL_PASSWORD=
MSSQL_HOST=127.0.0.1
MSSQL_PORT=1433

ORACLE_DB=
ORACLE_USER=
ORACLE_PASSWORD=
ORACLE_HOST=127.0.0.1
ORACLE_PORT=1521
ORACLE_SERVICE_NAME=

# Laboratorio académico. En producción liste orígenes explícitos.
CORS_ALLOW_ALL_ORIGINS=True
EOF
cp .env.example .env

Regla Estricta de Seguridad: El archivo .env contiene credenciales operativas y secretos de la aplicación (DJANGO_SECRET_KEY, contraseñas de bases de datos). Bajo ninguna circunstancia debe versionarse en Git. Su exclusión está garantizada por la regla .env presente en el archivo .gitignore creado en el ISS-00.

Módulo de Selección de Base de Datos: config/database.py

Este módulo no constituye una app de Django, sino una utilidad pura de infraestructura que traduce la variable DB_ENGINE a la estructura del diccionario DATABASES["default"].

"""Selección del motor de base de datos a partir del entorno.

Un solo alias, default, queda activo en cada ejecución.
"""

from django.core.exceptions import ImproperlyConfigured

ALLOWED_ENGINES = ("postgresql", "mysql", "mssql", "oracle")


def database_from_environ(env):
    engine = str(env.get("DB_ENGINE", "postgresql")).strip().lower()
    if engine not in ALLOWED_ENGINES:
        allowed = ", ".join(ALLOWED_ENGINES)
        raise ImproperlyConfigured(
            f"DB_ENGINE='{engine}' no es válido. Valores permitidos: {allowed}."
        )
    builders = {
        "postgresql": _postgresql,
        "mysql": _mysql,
        "mssql": _mssql,
        "oracle": _oracle,
    }
    return {"default": builders[engine](env)}


def _required(env, key):
    value = env.get(key)
    if value is None or str(value).strip() == "":
        raise ImproperlyConfigured(
            f"Falta la variable de entorno {key} para DB_ENGINE={env.get('DB_ENGINE')}."
        )
    return str(value).strip()


def _postgresql(env):
    return {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": _required(env, "POSTGRES_DB"),
        "USER": _required(env, "POSTGRES_USER"),
        "PASSWORD": _required(env, "POSTGRES_PASSWORD"),
        "HOST": _required(env, "POSTGRES_HOST"),
        "PORT": _required(env, "POSTGRES_PORT"),
    }


def _mysql(env):
    return {
        "ENGINE": "django.db.backends.mysql",
        "NAME": _required(env, "MYSQL_DB"),
        "USER": _required(env, "MYSQL_USER"),
        "PASSWORD": _required(env, "MYSQL_PASSWORD"),
        "HOST": _required(env, "MYSQL_HOST"),
        "PORT": _required(env, "MYSQL_PORT"),
        "OPTIONS": {"charset": "utf8mb4"},
    }


def _mssql(env):
    return {
        "ENGINE": "mssql",
        "NAME": _required(env, "MSSQL_DB"),
        "USER": _required(env, "MSSQL_USER"),
        "PASSWORD": _required(env, "MSSQL_PASSWORD"),
        "HOST": _required(env, "MSSQL_HOST"),
        "PORT": _required(env, "MSSQL_PORT"),
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
            "extra_params": "TrustServerCertificate=yes",
        },
    }


def _oracle(env):
    host = _required(env, "ORACLE_HOST")
    port = _required(env, "ORACLE_PORT")
    service = _required(env, "ORACLE_SERVICE_NAME")
    dsn = (
        f"(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST={host})(PORT={port}))"
        f"(CONNECT_DATA=(SERVICE_NAME={service})))"
    )
    return {
        "ENGINE": "django.db.backends.oracle",
        "NAME": dsn,
        "USER": _required(env, "ORACLE_USER"),
        "PASSWORD": _required(env, "ORACLE_PASSWORD"),
        "HOST": host,
        "PORT": "",
    }

Deconstrucción Analítica del Módulo database.py por Bloque de Código

4.1.1 Análisis de database_from_environ(env) y _required(env, key)
def database_from_environ(env):
    engine = str(env.get("DB_ENGINE", "postgresql")).strip().lower()
    if engine not in ALLOWED_ENGINES:
        allowed = ", ".join(ALLOWED_ENGINES)
        raise ImproperlyConfigured(
            f"DB_ENGINE='{engine}' no es válido. Valores permitidos: {allowed}."
        )
    builders = {
        "postgresql": _postgresql,
        "mysql": _mysql,
        "mssql": _mssql,
        "oracle": _oracle,
    }
    return {"default": builders[engine](env)}


def _required(env, key):
    value = env.get(key)
    if value is None or str(value).strip() == "":
        raise ImproperlyConfigured(
            f"Falta la variable de entorno {key} para DB_ENGINE={env.get('DB_ENGINE')}."
        )
    return str(value).strip()
  • Desglose Técnico: La función database_from_environ actúa como la puerta de entrada principal del módulo. Extrae la variable DB_ENGINE del diccionario enviado (env, habitualmente os.environ), eliminando espacios y convirtiendo la cadena a minúsculas. Si la cadena resultante no pertenece a ALLOWED_ENGINES (postgresql, mysql, mssql, oracle), interrumpe inmediatamente el arranque lanzando la excepción django.core.exceptions.ImproperlyConfigured.
  • Mecanismo de Invariante Fail-Fast: La función auxiliar _required evalúa la presencia y vacuidad de cada clave requerida. Si una variable es None o una cadena compuesta únicamente por espacios en blanco, lanza ImproperlyConfigured indicando exactamente cuál variable falta para el motor seleccionado. Esto evita que Django intente abrir conexiones con parámetros nulos o defectuosos en etapas avanzadas de la ejecución.
4.1.2 Análisis de _postgresql(env)
def _postgresql(env):
    return {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": _required(env, "POSTGRES_DB"),
        "USER": _required(env, "POSTGRES_USER"),
        "PASSWORD": _required(env, "POSTGRES_PASSWORD"),
        "HOST": _required(env, "POSTGRES_HOST"),
        "PORT": _required(env, "POSTGRES_PORT"),
    }
  • Desglose Técnico: Retorna el diccionario de configuración para el backend nativo de Django django.db.backends.postgresql. Utiliza _required para exigir las cinco variables indispensables (POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_HOST, POSTGRES_PORT), asegurando que el conector psycopg reciba todos los parámetros de red y autenticación necesarios.
4.1.3 Análisis de _mysql(env)
def _mysql(env):
    return {
        "ENGINE": "django.db.backends.mysql",
        "NAME": _required(env, "MYSQL_DB"),
        "USER": _required(env, "MYSQL_USER"),
        "PASSWORD": _required(env, "MYSQL_PASSWORD"),
        "HOST": _required(env, "MYSQL_HOST"),
        "PORT": _required(env, "MYSQL_PORT"),
        "OPTIONS": {"charset": "utf8mb4"},
    }
  • Desglose Técnico: Vincula el motor django.db.backends.mysql sobre el controlador mysqlclient. Incorpora de forma implícita la clave "OPTIONS": {"charset": "utf8mb4"}. Esta configuración es mandatoria para forzar a MySQL/MariaDB a operar con la codificación de caracteres UTF-8 de 4 bytes completa, previniendo la corrupción de datos ante caracteres especiales, acentos o emojis.
4.1.4 Análisis de _mssql(env)
def _mssql(env):
    return {
        "ENGINE": "mssql",
        "NAME": _required(env, "MSSQL_DB"),
        "USER": _required(env, "MSSQL_USER"),
        "PASSWORD": _required(env, "MSSQL_PASSWORD"),
        "HOST": _required(env, "MSSQL_HOST"),
        "PORT": _required(env, "MSSQL_PORT"),
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
            "extra_params": "TrustServerCertificate=yes",
        },
    }
  • Desglose Técnico: Asigna el valor corto "ENGINE": "mssql". A diferencia de los motores integrados de Django, mssql-django se registra como una librería de terceros que expone el identificador "mssql". Dentro del subdiccionario "OPTIONS", declara explícitamente la cadena del driver del sistema operativo (ODBC Driver 18 for SQL Server) y el parámetro TrustServerCertificate=yes para permitir conexiones TLS/SSL locales sin requerir la instalación previa de un certificado de CA autofirmado en la máquina de desarrollo.
4.1.5 Análisis de _oracle(env)
def _oracle(env):
    host = _required(env, "ORACLE_HOST")
    port = _required(env, "ORACLE_PORT")
    service = _required(env, "ORACLE_SERVICE_NAME")
    dsn = (
        f"(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST={host})(PORT={port}))"
        f"(CONNECT_DATA=(SERVICE_NAME={service})))"
    )
    return {
        "ENGINE": "django.db.backends.oracle",
        "NAME": dsn,
        "USER": _required(env, "ORACLE_USER"),
        "PASSWORD": _required(env, "ORACLE_PASSWORD"),
        "HOST": host,
        "PORT": "",
    }
  • Desglose Técnico: Modela la conexión hacia Oracle Database mediante el controlador oracledb. En lugar de pasar un nombre de base de datos simple en NAME, construye un descriptor DSN estructurado de baja capa utilizando la sintaxis de red de Oracle con el parámetro SERVICE_NAME.
  • Regla Crítica del Puerto Vacío ("PORT": ""): En el diccionario retornado, se exige ORACLE_PORT para formatear la cadena dsn, pero fuerza deliberadamente "PORT": "". Si Django detecta un número de puerto en la clave PORT, su backend interno altera la consulta de conexión e intenta interpretar el valor de NAME como un SID tradicional en lugar de un DSN. Al fijar "PORT": "", se obliga a Django a utilizar el valor de NAME de forma literal como una cadena de conexión DSN completa.

Paquete Inicial de Settings: config/settings/__init__.py

"""Configuración de StoreLab.

El paquete sustituye al settings.py generado por startproject.
DJANGO_SETTINGS_MODULE sigue siendo config.settings.
"""

import os
from pathlib import Path

# python-dotenv, instalado en este ISS. Lee .env antes de SECRET_KEY y DATABASES.
from dotenv import load_dotenv

from config.database import database_from_environ

BASE_DIR = Path(__file__).resolve().parent.parent.parent
load_dotenv(BASE_DIR / ".env")

SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "").strip()
if not SECRET_KEY:
    from django.core.exceptions import ImproperlyConfigured

    raise ImproperlyConfigured("Defina DJANGO_SECRET_KEY en el archivo .env.")

DEBUG = os.environ.get("DJANGO_DEBUG", "False").strip().lower() in {"1", "true", "yes", "on"}

ALLOWED_HOSTS = [
    host.strip()
    for host in os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost,127.0.0.1").split(",")
    if host.strip()
]

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
]

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

ROOT_URLCONF = "config.urls"

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]

WSGI_APPLICATION = "config.wsgi.application"
ASGI_APPLICATION = "config.asgi.application"

DATABASES = database_from_environ(os.environ)

AUTH_PASSWORD_VALIDATORS = [
    {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
    {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
    {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
    {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
]

LANGUAGE_CODE = "es"
TIME_ZONE = "America/Bogota"
USE_I18N = True
USE_TZ = True

STATIC_URL = "static/"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

El módulo __init__.py ejecuta load_dotenv(BASE_DIR / ".env") de manera inmediata para cargar el entorno local. Establece la zona horaria en America/Bogota, el idioma en es con soporte I18N/TZ activo, valida de forma estricta que DJANGO_SECRET_KEY no esté vacía e invoca a database_from_environ(os.environ) para poblar el diccionario DATABASES.

Definida la arquitectura modular de la configuración, es preciso implementar la protección automatizada que evita la ejecución errónea bajo el intérprete global del sistema operativo.


5.0 Re-ejecución del Intérprete y Parches en los Puntos de Entrada

5.1 Mecanismo Anti-Error de Intérprete del Sistema (config/project_python.py)

Un problema recurrente en equipos de desarrollo ocurre cuando un desarrollador ejecuta el comando python3 manage.py en la terminal sin haber activado previamente el entorno virtual .venv. En esta situación, el sistema utiliza el intérprete global, el cual no posee instalados paquetes como python-dotenv, Django o los drivers de base de datos, generando fallos inmediatos por módulos no encontrados (ImportError).

Para solucionar esta fragilidad de forma totalmente transparente, se introduce el módulo config/project_python.py. Este módulo evalúa en tiempo de arranque si el proceso en ejecución está utilizando el Python del entorno .venv. De no ser así, re-ejecuta de manera automática el comando sustituyendo el proceso actual en memoria por el intérprete correcto alojado en .venv/bin/python.

"""Intérprete del proyecto.

Los paquetes se instalan dentro de .venv. El comando python3 manage.py
usa el Python del sistema y no ve esos paquetes.
use_project_python() vuelve a lanzar el mismo comando con .venv/bin/python.
"""

import os
import sys
from pathlib import Path

PROJECT_ROOT = Path(__file__).resolve().parent.parent


def use_project_python():
    """Reejecuta el proceso con el Python de .venv si aún no es ese intérprete."""
    venv_root = PROJECT_ROOT / ".venv"
    venv_python = venv_root / "bin" / "python"
    if not venv_python.is_file():
        return
    try:
        if Path(sys.prefix).resolve() == venv_root.resolve():
            return
    except OSError:
        return
    os.execv(venv_python, [str(venv_python), *sys.argv])

Análisis del Mecanismo use_project_python()

El script resuelve la ruta raíz del proyecto y verifica la existencia del binario .venv/bin/python. Compara la ruta resuelta del prefijo del sistema (Path(sys.prefix).resolve()) contra la raíz del entorno virtual (venv_root.resolve()). La comparación se realiza obligatoriamente sobre sys.prefix y no sobre sys.executable debido a que el binario de un entorno virtual en sistemas Linux/macOS es un enlace simbólico que apunta de regreso hacia el ejecutable global del sistema. Si las rutas no coinciden, invoca la llamada al sistema os.execv, la cual reemplaza la imagen del proceso en memoria por una nueva ejecución invocando directamente .venv/bin/python y preservando la totalidad de los argumentos pasados originalmente en sys.argv.

Parcheado Estricto de los Puntos de Entrada del Sistema

Para activar este mecanismo transparente y asegurar que .env sea leído antes de cargar cualquier componente de Django, se aplican parches explícitos en manage.py, config/wsgi.py y config/asgi.py:

1. Parche Completo en manage.py
  • UBICAR:
    import os
    import sys
    
  • REEMPLAZAR POR:
    import sys
    from pathlib import Path
    
    _ROOT = Path(__file__).resolve().parent
    if str(_ROOT) not in sys.path:
        sys.path.insert(0, str(_ROOT))
    
    from config.project_python import use_project_python
    
    use_project_python()
    
    import os
    
    # python-dotenv se instaló en este ISS, dentro de .venv.
    from dotenv import load_dotenv
    
  • UBICAR:
        """Run administrative tasks."""
    
  • AGREGAR DEBAJO:
        load_dotenv()
    
2. Parche Completo en config/wsgi.py
  • UBICAR:
    import os
    
    from django.core.wsgi import get_wsgi_application
    
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
    
  • REEMPLAZAR POR:
    import os
    
    # python-dotenv (este ISS) carga .env en el intérprete que arrancó el proceso.
    from dotenv import load_dotenv
    
    from django.core.wsgi import get_wsgi_application
    
    load_dotenv()
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
    
3. Parche Completo en config/asgi.py
  • UBICAR:
    import os
    
    from django.core.asgi import get_asgi_application
    
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
    
  • REEMPLAZAR POR:
    import os
    
    # python-dotenv (este ISS) carga .env en el intérprete que arrancó el proceso.
    from dotenv import load_dotenv
    
    from django.core.asgi import get_asgi_application
    
    load_dotenv()
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
    

6.0 Flujo de Ejecución del Entorno y Resolución de Invariantes

6.1 Traza de Carga de Configuración

En tiempo de ejecución, la información de infraestructura fluye a través de una cadena secuencial estricta desde los archivos planos de texto hasta quedar representada en las estructuras internas de Django.

Diagrama del Flujo de Datos

  ┌──────────────┐
  │  Archivo     │
  │    .env      │
  └──────┬───────┘
         │ (1) Inyección de variables
         ▼
  ┌──────────────┐
  │ load_dotenv()│
  └──────┬───────┘
         │ (2) Poblamiento de os.environ (respetando variables preexistentes)
         ▼
  ┌────────────────────────┐
  │ settings/__init__.py   │
  └──────┬─────────────────┘
         │ (3) Invocación con os.environ
         ▼
  ┌──────────────────────────────┐
  │ database_from_environ(env)   │
  └──────┬───────────────────────┘
         │ (4) Selección e inspección de DB_ENGINE
         ▼
  ┌────────────────────────┐
  │  DATABASES['default']  │
  └──────┬─────────────────┘
         │ (5) Diccionario configurado
         ▼
  ┌────────────────────────┐
  │     ORM de Django      │
  └────────────────────────┘

Invariantes Arquitectónicas de la Solución

  1. Un Solo Alias Activo: En el diccionario DATABASES expuesto por Django solo existe activado el alias "default" en cada proceso. El ORM opera de forma agnóstica sin conocer cuál motor físico está respondiendo por debajo.
  2. Precedencia de Sobreescritura CLI (CLI Overrides): La función load_dotenv() está diseñada por especificación para no sobrescribir variables de entorno que ya hayan sido definidas explícitamente en la sesión del shell. Esta propiedad permite alternar el motor de base de datos sobre la marcha anteponiendo la variable en la línea de comandos sin modificar el archivo .env.

Demostración Práctica: Traza de Sobreescritura CLI

Supóngase que el archivo .env contiene la configuración por defecto para PostgreSQL (DB_ENGINE=postgresql). Si el desarrollador desea validar el motor MySQL en una ejecución aislada, invoca el comando inyectando la variable en el shell:

# Invocación con inyección de variable de entorno por CLI:
DB_ENGINE=mysql python manage.py check

Traza Interna de Precedencia del Proceso: 1. El Shell del sistema operativo inicializa el proceso hijo de Python asignando os.environ["DB_ENGINE"] = "mysql". 2. manage.py intercepta el proceso mediante use_project_python(), preservando el diccionario de entorno os.environ. 3. Se ejecuta load_dotenv(). Al inspeccionar os.environ, detecta que DB_ENGINE ya posee el valor "mysql". Por ende, omite la lectura de esa clave desde .env. 4. settings/__init__.py evalúa database_from_environ(os.environ). 5. database_from_environ recibe "mysql", invoca a _mysql(os.environ) y configura DATABASES["default"] con la clave "ENGINE": "django.db.backends.mysql". 6. La comprobación del sistema de Django valida la configuración contra MySQL sin alterar físicamente el archivo .env del disco.


7.0 Diagnóstico de Errores Frecuentes y Soluciones

7.1 Matriz de Fallos Comunes en ISS-01

Escenario de Error Causa Raíz Efecto Observado Solución Paso a Paso
Instalación Global de Librerías Ejecución del comando pip sin haber activado previamente el entorno virtual con source .venv/bin/activate. Los paquetes se instalan en el Python global. El directorio .venv permanece sin los drivers. python -m pip freeze no refleja dependencias. 1. Ejecute source .venv/bin/activate.
2. Verifique con which python que apunte a .venv/bin/python.
3. Reinstale los paquetes mediante python -m pip install -r requirements.txt.
Variable Obligatoria Faltante Ausencia o vacuidad de una clave requerida en .env (ej. POSTGRES_DB="") para el motor activo. El arranque del proceso se interrumpe y la función _required lanza la excepción ImproperlyConfigured. 1. Abra el archivo .env.
2. Localice la sección correspondiente al DB_ENGINE seleccionado.
3. Asigne un valor no vacío a la variable reportada en la traza.
Declaración de Motor No Permitido Asignación de un nombre de motor no soportado en .env (ejemplo: DB_ENGINE=sqlite). La función database_from_environ valida contra ALLOWED_ENGINES y lanza ImproperlyConfigured. 1. Abra el archivo .env.
2. Corrija la variable DB_ENGINE utilizando únicamente: postgresql, mysql, mssql o oracle.
Omisión del Driver ODBC del SO Se selecciona DB_ENGINE=mssql pero la máquina cliente no tiene instalado el driver C nativo de Microsoft. Al intentar conectar, la librería pyodbc lanza un error del sistema operativo por imposibilidad de cargar el driver dinámico. 1. Instale en el sistema operativo el paquete oficial ODBC Driver 18 for SQL Server.
2. Verifique en database.py que la cadena "driver" coincida con el nombre exacto del driver instalado.
Error de Sintaxis en DSN de Oracle La función _oracle no vacía la clave PORT, provocando que Django interprete la cadena como un SID. La conexión falla con error de sintaxis de red Oracle (ORA-12514 o ORA-12541). 1. Abra config/database.py.
2. Confirme que la función _oracle retorne obligatoriamente "PORT": "".
3. Asegúrese de que ORACLE_SERVICE_NAME esté asignado en .env.

8.0 Matriz de Aceptación, Evidencias y Tabla GATE

8.1 Verificación Formal del Incremento

La evaluación del incremento ISS-01 sigue una metodología formal basada en Criterios de Aceptación (AC), Evidencias Técnicas de ejecución (EVI) y una Puerta de Control (GATE) que determina la aprobación del estado del sistema.

Criterios de Aceptación Detallados

  • AC-01-01: Las cuatro funciones generadoras (postgresql, mysql, mssql, oracle) producen exactamente la clave ENGINE documentada para cada backend.
  • AC-01-02: La especificación de un motor no permitido (ej. DB_ENGINE=sqlite) falla de manera inmediata lanzando la excepción ImproperlyConfigured.
  • AC-01-03: La ausencia o vacuidad de una variable requerida para el motor activo detiene la configuración lanzando ImproperlyConfigured antes de intentar la conexión.
  • AC-01-04: El DSN generado dinámicamente para el motor Oracle contiene de forma explícita el parámetro SERVICE_NAME.

Evidencia Técnica Asociada

  • EVI-01-01: Ejecución exitosa de la suite de pruebas unitarias DatabaseSelectionTests (definida formalmente e integrada en el proyecto durante el ISS-03 dentro de apps/security/tests.py). La suite utiliza SimpleTestCase para validar la lógica pura de selección de base de datos en memoria sin requerir la apertura de sockets de red reales. Para el alcance del ISS-01, la verificación temporal de sistema se efectúa mediante python manage.py check.

Tabla del GATE Formal

AC Verificación Evidencia Resultado
AC-01-01 Test por motor EVI-01-01 PASS
AC-01-02 Test de motor inválido EVI-01-01 PASS
AC-01-03 Test de variable vacía EVI-01-01 PASS
AC-01-04 Aserción SERVICE_NAME EVI-01-01 PASS

Habiendo auditado y alcanzado el estado PASS en la totalidad de las verificaciones del GATE, la sección final presenta una evaluación teórica orientada a consolidar la defensa del trabajo realizado.


9.0 Cuestionario de Defensa Oral y Evaluación Técnica

9.1 Preguntas de Evaluación Teórico-Práctica

Las siguientes preguntas están diseñadas para evaluar la comprensión conceptual profunda y las decisiones de diseño arquitectónico tomadas durante la ejecución del ISS-01, superando la mera memorización de comandos CLI.

1. ¿Por qué se requiere reemplazar el archivo config/settings.py generado originalmente por Django por un paquete de Python config/settings/ con un archivo __init__.py?

Respuesta Pautada: En Python, un archivo de módulo y una carpeta de paquete no pueden coexistir con el mismo nombre en un mismo nivel de directorio. El comando startproject crea un archivo simple config/settings.py. Para evolucionar hacia una arquitectura modular sin romper la variable de entorno DJANGO_SETTINGS_MODULE="config.settings", es necesario eliminar el archivo monolítico settings.py y crear el directorio config/settings/. Al incluir un archivo __init__.py dentro de dicho directorio, Python interpreta el paquete config.settings como un módulo importable estándar, permitiendo fragmentar la configuración interna manteniendo una ruta de importación estable para el resto del sistema.

2. ¿Cuál es el mecanismo técnico mediante el cual config/project_python.py evita el uso accidental del Python del sistema al ejecutar python3 manage.py? Explique el rol de sys.prefix y os.execv.

Respuesta Pautada: La función use_project_python() resuelve la ruta física del directorio del entorno virtual .venv. Compara la ruta de sys.prefix (que indica la raíz de instalación del intérprete actualmente en ejecución) con la ruta resuelta de .venv. Se utiliza sys.prefix en lugar de sys.executable porque el ejecutable binario en un entorno virtual suele ser un enlace simbólico hacia el Python del sistema operativo. Si las rutas no coinciden, significa que el usuario ejecutó el comando con el Python global. En ese punto, el script invoca la llamada al sistema os.execv(venv_python, [str(venv_python), *sys.argv]), la cual reemplaza de forma transparente la imagen del proceso actual en memoria por una nueva instancia del ejecutable .venv/bin/python, preservando exactamente los mismos argumentos pasados por la CLI (sys.argv).

3. ¿Por qué la función _oracle fuerza un parámetro PORT vacío ("") en el diccionario resultante de la configuración de base de datos?

Respuesta Pautada: En la capa de abstracción de base de datos de Django para Oracle (django.db.backends.oracle), si la clave PORT dentro de DATABASES["default"] contiene una cadena con un número de puerto (ejemplo: "1521"), el backend intenta construir internamente la conexión asumiendo una sintaxis de SID tradicional. Al forzar la clave PORT a una cadena vacía (""), se obliga a Django a tomar el contenido presente en el campo NAME como un descriptor de conexión literal DSN (Data Source Name). Esto permite inyectar la cadena estructurada con el parámetro ORACLE_SERVICE_NAME requerida por instancias modernas de Oracle sin interferencias de la lógica por defecto del backend.

4. ¿Qué diferencia técnica existe entre la forma en que mssql-django y los conectores nativos de Django registran sus motores en la clave ENGINE?

Respuesta Pautada: Los conectores nativos incluidos e integrados en el núcleo de Django utilizan rutas de importación de módulos Python completas dentro del espacio de nombres del marco de trabajo (por ejemplo, django.db.backends.postgresql o django.db.backends.mysql). Por el contrario, el adaptador para SQL Server es una librería de terceros (mssql-django). Este paquete registra explícitamente un alias corto denominado "mssql" dentro del punto de entrada de conectores de Django. Por lo tanto, al configurar SQL Server, la clave ENGINE debe declararse literalmente como "ENGINE": "mssql", y no mediante el espacio de nombres tradicional de Django.

5. ¿Por qué la función load_dotenv() no sobrescribe variables de entorno previamente definidas en la sesión de la terminal y qué ventaja práctica ofrece esto durante las pruebas?

Respuesta Pautada: Por diseño y comportamiento estándar, la función load_dotenv() respeta las variables que ya existen previamente en el diccionario del sistema os.environ. Si una variable ya fue declarada en el entorno del proceso padre (la terminal), load_dotenv() no reemplaza su valor con el contenido del archivo .env. La ventaja práctica fundamental de este comportamiento es que permite realizar sobreescrituras sobre la marcha en la línea de comandos (CLI Overrides). Un desarrollador o un pipeline de CI/CD puede ejecutar comandos como DB_ENGINE=mysql python manage.py check para probar temporalmente un motor distinto en un proceso aislado sin necesidad de modificar ni alterar físicamente los archivos .env o el código fuente del proyecto.


Navegación de la ruta: ← ISS-00 · 🛠 Construir · ↑ Ruta Django · → ISS-01 · 🛠 Construir