Saltar a contenido

📚 Unidad ISS-05 · ProductType y Product — 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 ProductType y Product 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. Objetivos y Requisitos de ISS-05 Objetivo
Recorrido 6. Secuencia de Comandos CLI y Migraciones Construcción
Cierre 9. Criterios de Aceptación (AC-05-01 al AC-05-04) Criterios de aceptación · GATE
Evaluación 11. Cuestionario de Defensa Oral Técnica GATE

🖥 Presentación del ISS

Presentación de la unidad. Diapositivas de ISS-05 — ProductType y Product (13 diapositivas). Se visualiza aquí, dentro del sitio.

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

13 diapositivas · se visualiza dentro del sitio.

🎬 Video explicativo

Recorrido audiovisual de la unidad. El video recorre el ISS técnico de ISS-05 — ProductType y Product, bloque por bloque.

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

Infografía

Infografía de la unidad

Mapa conceptual

  • Modelo ProductType
  • name: max_length=100, unique=True, MinLengthValidator(2)
  • description: max_length=255, null=True, blank=True
  • db_table: product_types
  • ordering: name, id
  • constraint: product_types_status_valid
  • Modelo Product
  • product_type: ForeignKey a ProductType, on_delete=RESTRICT
  • db_column: product_type_id
  • name: max_length=120, unique=True, MinLengthValidator(2)
  • description: max_length=255, null=True, blank=True
  • price: DecimalField(max_digits=12, decimal_places=2, MinValueValidator)
  • stock: PositiveIntegerField, default=0
  • db_table: products
  • ordering: name, id
  • Reglas de Integridad
  • Relación: ProductType 1 a N Product
  • Restricción: Borrar tipo referenciado lanza RestrictedError
  • Comportamiento: on_delete=RESTRICT
  • Constraints y Validaciones
  • Constraint estado: products_status_valid
  • Constraint precio: product_price_non_neg (price >= 0)
  • Validators: MinValueValidator y MinLengthValidator
  • Migraciones CLI
  • makemigrations: python manage.py makemigrations product
  • migrate: python manage.py migrate
  • Verificación y Gate
  • Criterios de Aceptación: AC-05-01 a AC-05-04
  • Verificación: ProductApiTests.test_default_status_and_type_restriction
  • Evidencias: EVI-05-01 (migración) y EVI-05-02 (RestrictedError)
  • Resultado: PASS

Guía de Estudio Exhaustiva — ISS-05: Modelado de Catálogo (ProductType y Product)


1. Objetivos y Requisitos de ISS-05

El propósito central del módulo ISS-05 es el modelado relacional y la persistencia del catálogo comercial en la aplicación apps/product/. En esta fase del proyecto de software, se establece la relación de integridad referencial de tipo $1 \to N$ entre la entidad clasificadora (ProductType) y los ítems del inventario (Product), garantizando la consistencia y la protección de datos mediante restricciones defensivas aplicadas en paralelo en el ORM de Django y en el motor de base de datos relacional.

Requisitos Previos

  • Superación Definitiva de ISS-04: Es indispensable contar con la clase abstracta StoreLabModel (ubicada en apps/common/models.py), la cual provee la auditoría temporal (created_at, updated_at) y la gestión normalizada de estado (status con valor predeterminado 'inactive' y la restricción CheckConstraint reutilizable).
  • Entorno Virtual e Intérprete Configurado: Intérprete Python activo mediante .venv/bin/python con Django 5.2.17 e infraestructura relacional operativa.

Marco Conceptual y Protección de Datos del Dominio

El catálogo comercial es el núcleo transaccional del sistema StoreLab. Proteger la integridad del dominio exige prevenir la eliminación accidental o en cascada de categorías que contengan artículos asociados. Asimismo, se impone un respeto irrestricto al diccionario de datos de StoreLab: queda estrictamente prohibido introducir campos no especificados (como marca, stock mínimo o cantidad por empaque), ya que contaminan la arquitectura del dominio e invalidan la suite de pruebas automatizadas.

Objetivos Estructurados de la Unidad

  • Modelar la Entidad ProductType: Estructurar la tabla física product_types para representar categorías comerciales con nombres únicos, validación de longitud mínima y estado operacional heredado.
  • Modelar la Entidad Product: Persistir los artículos en la tabla products con sus atributos operativos (name, description, price, stock) e integrando la clave foránea obligatoria hacia ProductType.
  • Implementar Integridad Referencial Estricta ($1 \to N$): Configurar la clave foránea mediante la política on_delete=models.RESTRICT para bloquear cualquier intento de eliminación de un ProductType referenciado por productos activos.
  • Garantizar la Precisión Monetaria y de Inventario: Implementar DecimalField para prevenir imprecisiones de punto flotante en montos financieros y PositiveIntegerField para inventarios, blindando los datos mediante validadores del ORM (MinValueValidator) y restricciones del motor SQL (CheckConstraint).

2. Conceptos Esenciales y Vocabulario Técnico

El desarrollo del ISS-05 requiere el dominio preciso de las siguientes clases, argumentos y mecanismos del ORM de Django y del motor de base de datos relacional:

Concepto ORM Definición Exacta Función Técnica en ISS-05 Impacto en la Base de Datos Relacional
ForeignKey Campo relacional del ORM que implementa una clave foránea para establecer una relación $1 \to N$. Relaciona cada Product con su ProductType correspondiente a través del parámetro explícito db_column="product_type_id". Crea una columna product_type_id de tipo BIGINT con un índice de búsqueda y restricción FOREIGN KEY.
on_delete=RESTRICT Regla de integridad referencial que prohíbe la eliminación de un registro padre en la BD si posee registros hijos vinculados. Previene la eliminación accidental de un ProductType si existen instancias de Product asociadas a este. Genera una restricción física de clave foránea con comportamiento ON DELETE RESTRICT (o emulado por la capa ORM).
RestrictedError Excepción en tiempo de ejecución (django.db.models.deletion.RestrictedError) que eleva el ORM al intentar eliminar un objeto protegido por RESTRICT. Captura el intento inválido de eliminar un ProductType con productos dependientes durante la ejecución de pruebas unitarias y flujos de negocio. Interrumpe la transacción en la capa de aplicación antes de enviar una sentencia SQL DELETE a la base de datos.
DecimalField Campo de mapeo relacional para números de punto fijo con precisión decimal exacta. Almacena el valor del precio (price), evitando imprecisiones de representación binaria inherentes a FloatField. Mapea a una columna de tipo NUMERIC(12,2) o DECIMAL(12,2) (10 dígitos enteros y 2 decimales).
MinValueValidator Validador declarativo de django.core.validators que opera en la capa de aplicación u ORM. Valida que el precio ingresado no sea inferior a cero (Decimal("0")), previniendo precios negativos antes de persistir. Actúa en la memoria de la aplicación durante la invocación de full_clean(). Retorna un ValidationError.
PositiveIntegerField Campo para números enteros no negativos ($0 \dots 2.147.483.647$). Almacena la cantidad de inventario disponible (stock) con un valor predeterminado de $0$. Mapea a una columna SQL INTEGER con restricción de verificación implícita o explícita (stock >= 0).
CheckConstraint Restricción condicional evaluada directamente en la base de datos relacional ($Q(\text{price} \ge 0)$). Garantiza que $Q(\text{price} \ge 0)$ y valida que el campo status pertenezca a los valores de RecordStatus. Agrega una restricción física CHECK (price >= 0) en la definición de la tabla dentro del motor relacional.

Glosario Técnico Explicativo

Precisiones Monetarias con DecimalField vs. FloatField

En sistemas bancarios y comerciales, el uso de FloatField es una mala práctica crítica debido a la representación binaria de punto flotante definida por el estándar IEEE 754. Dicho estándar no puede representar con precisión fracciones decimales como $0.1$ o $0.01$, introduciendo errores de redondeo acumulativos (por ejemplo, $0.1 + 0.2 = 0.30000000000000004$). El tipo DecimalField mapea directamente al tipo nativo NUMERIC o DECIMAL del motor relacional, almacenando dígitos BCD (Binary-Coded Decimal) o enteros escalados de precisión exacta. Al configurar max_digits=12 y decimal_places=2, el ORM garantiza la conservación exacta de los montos monetarios hasta un entero de 10 dígitos.

Intercepción y Mecánica de RestrictedError

Cuando se invoca el método .delete() sobre una instancia del modelo padre (ProductType), el ORM de Django realiza una inspección previa en la memoria de la aplicación antes de emitir cualquier orden SQL destructiva. Si la relación ForeignKey está configurada con on_delete=models.RESTRICT, Django ejecuta una consulta de verificación para comprobar si existen registros en la tabla hija (products) cuya clave foránea apunte al registro que se pretende eliminar. Si la consulta retorna uno o más registros dependientes, Django detiene de inmediato el flujo de la transacción y eleva la excepción django.db.models.deletion.RestrictedError. Esto impide que la sentencia DELETE sea enviada al motor SQL, protegiendo los datos sin consumir recursos innecesarios en la base de datos.


3. Estructura de Archivos y Ubicación del Código

La implementación del módulo de catálogo se desarrolla dentro de la aplicación apps/product/. A continuación se detalla el árbol de archivos antes y después de la construcción del ISS-05:

Árbol de Directorios del Proyecto (apps/product/)

Estado Inicial (Antes de ISS-05)

apps/product/
├── __init__.py
├── admin.py
├── apps.py
├── migrations/
│   └── __init__.py
├── models.py       # Archivo vacío o con comentarios por defecto de startapp
├── tests.py
└── views.py

Estado Resultante (Después de ISS-05)

apps/product/
├── __init__.py
├── admin.py
├── apps.py
├── migrations/
│   ├── 0001_initial.py  # Archivo de migración física generado automáticamente
│   └── __init__.py
├── models.py            # Modelos ProductType y Product implementados
├── tests.py
└── views.py

Comentario Arquitectura de Migraciones: El archivo apps/product/migrations/0001_initial.py es generado exclusivamente de forma automática mediante la ejecución de python manage.py makemigrations product. Bajo ninguna circunstancia debe crearse o editarse manualmente, salvo en escenarios avanzados donde se requiera escribir operaciones SQL personalizadas (Custom Migration Operations).

Jerarquía de Herencia de Modelos

El archivo objetivo a modificar es exclusivamente apps/product/models.py. Ambos modelos heredan de StoreLabModel (ubicado en apps/common/models.py), el cual a su vez deriva de TimeStampedModel.

TimeStampedModel (abstracto: created_at, updated_at)
       │
       ▼
StoreLabModel (abstracto: status, status_constraint())
       │
       ├──────────────────────────┐
       ▼                          ▼
  ProductType                  Product

4. Explicación y Desglose por Bloques Semánticos (apps/product/models.py)

Código Completo de apps/product/models.py

from decimal import Decimal

from django.core.validators import MinLengthValidator, MinValueValidator
from django.db import models

from apps.common.models import StoreLabModel


class ProductType(StoreLabModel):
    name = models.CharField(max_length=100, unique=True, validators=[MinLengthValidator(2)])
    description = models.CharField(max_length=255, null=True, blank=True)

    class Meta:
        db_table = "product_types"
        verbose_name = "tipo de producto"
        verbose_name_plural = "tipos de producto"
        ordering = ["name", "id"]
        constraints = [StoreLabModel.status_constraint("product_types_status_valid")]

    def __str__(self):
        return self.name


class Product(StoreLabModel):
    product_type = models.ForeignKey(
        ProductType,
        on_delete=models.RESTRICT,
        related_name="products",
        db_column="product_type_id",
    )
    name = models.CharField(max_length=120, unique=True, validators=[MinLengthValidator(2)])
    description = models.CharField(max_length=255, null=True, blank=True)
    price = models.DecimalField(
        max_digits=12,
        decimal_places=2,
        validators=[MinValueValidator(Decimal("0"))],
    )
    stock = models.PositiveIntegerField(default=0)

    class Meta:
        db_table = "products"
        verbose_name = "producto"
        verbose_name_plural = "productos"
        ordering = ["name", "id"]
        constraints = [
            StoreLabModel.status_constraint("products_status_valid"),
            models.CheckConstraint(
                condition=models.Q(price__gte=0),
                name="product_price_non_neg",
            ),
        ]

    def __str__(self):
        return self.name

Descomposición por Bloques Semánticos

4.1 Imports y Validadores

from decimal import Decimal

from django.core.validators import MinLengthValidator, MinValueValidator
from django.db import models

from apps.common.models import StoreLabModel
* QUÉ ES: Módulo de importación de utilidades numéricas de precisión, validadores estándar de Django, conectores del ORM relacional y el modelo abstracto base del proyecto. * QUÉ HACE: Carga Decimal para la instanciación estricta de límites numéricos monetarios; MinLengthValidator y MinValueValidator para imponer reglas de longitud de cadena y rangos numéricos; e importa StoreLabModel para heredar auditoría temporal y control de estados. * POR QUÉ EXISTE: Provee la infraestructura de tipos e instrumentos de validación necesarios para validar los datos en la memoria de la aplicación antes de intentar la persistencia en SQL. * CON QUÉ SE RELACIONA: Con el archivo apps/common/models.py y con la capa de validación de Django (full_clean()) y Django REST Framework.


4.2 Modelo ProductType

class ProductType(StoreLabModel):
    name = models.CharField(max_length=100, unique=True, validators=[MinLengthValidator(2)])
    description = models.CharField(max_length=255, null=True, blank=True)

    class Meta:
        db_table = "product_types"
        verbose_name = "tipo de producto"
        verbose_name_plural = "tipos de producto"
        ordering = ["name", "id"]
        constraints = [StoreLabModel.status_constraint("product_types_status_valid")]

    def __str__(self):
        return self.name
* QUÉ ES: Definición del modelo relacional para la entidad clasificadora de categorías o tipos de producto. * QUÉ HACE: Registra la entidad vinculada a la tabla física product_types. Exige un nombre único de entre 2 y 100 caracteres, admite una descripción opcional, hereda los campos de auditoría (created_at, updated_at) y estado (status), y define un ordenamiento por defecto determinista por name e id. Además, aplica la restricción product_types_status_valid sobre los valores permitidos en status. * POR QUÉ EXISTE: Normaliza la taxonomía del catálogo, categorizando los artículos de inventario de forma estructurada. * CON QUÉ SE RELACIONA: Actúa como la entidad padre en la relación $1 \to N$ con el modelo Product.


4.3 Modelo Product

class Product(StoreLabModel):
    product_type = models.ForeignKey(
        ProductType,
        on_delete=models.RESTRICT,
        related_name="products",
        db_column="product_type_id",
    )
    name = models.CharField(max_length=120, unique=True, validators=[MinLengthValidator(2)])
    description = models.CharField(max_length=255, null=True, blank=True)
    price = models.DecimalField(
        max_digits=12,
        decimal_places=2,
        validators=[MinValueValidator(Decimal("0"))],
    )
    stock = models.PositiveIntegerField(default=0)

    class Meta:
        db_table = "products"
        verbose_name = "producto"
        verbose_name_plural = "productos"
        ordering = ["name", "id"]
        constraints = [
            StoreLabModel.status_constraint("products_status_valid"),
            models.CheckConstraint(
                condition=models.Q(price__gte=0),
                name="product_price_non_neg",
            ),
        ]

    def __str__(self):
        return self.name
* QUÉ ES: Definición del modelo relacional para la representación de los artículos individuales del catálogo comercial de StoreLab. * QUÉ HACE: Establece un vínculo obligatorio con ProductType mediante la clave foránea product_type_id bajo la regla RESTRICT. Garantiza nombres únicos (de 2 a 120 caracteres), maneja montos económicos con DecimalField (máximo 12 dígitos, 2 decimales, no negativo), asigna un stock inicial de $0$ e incorpora las restricciones físicas products_status_valid y product_price_non_neg. * POR QUÉ EXISTE: Modela la oferta comercial vendible del sistema de inventarios garantizando la consistencia financiera e impidiendo la existencia de productos huérfanos. * CON QUÉ SE RELACIONA: Con ProductType como entidad contenedora y, en fases posteriores, con la tabla de detalles de venta (ProductSale).


5. Análisis Comparativo de Comportamientos de on_delete

La elección de la política de eliminación en claves foráneas define la robustez de la arquitectura de datos del sistema.

Regla on_delete Comportamiento en BD al eliminar el registro Padre (ProductType) Riesgo en el Dominio Comercial de StoreLab
CASCADE Elimina automáticamente y en forma de dominó todos los registros hijos (products) vinculados al padre borrado. CRÍTICO: Borrado catastrófico de productos activos del catálogo. Destruye la trazabilidad histórica de inventarios y ventas.
SET_NULL Desvincula los registros hijos asignando el valor NULL a la columna product_type_id. ALTO: Genera productos huérfanos sin categoría asociada. Exige que la columna permita nulos (null=True), violando el modelo de dominio.
RESTRICT Bloquea de forma absoluta el borrado del Padre. Lanza la excepción RestrictedError si existen registros hijos asociados. NINGUNO: Garantiza la integridad referencial y de negocio. Ninguna categoría en uso puede ser eliminada.

Justificación Técnica de RESTRICT en StoreLab

En la arquitectura de StoreLab, RESTRICT es la única política válida. ProductType representa la clasificación taxonómica sobre la cual se estructuran las búsquedas, reportes y métricas de venta. Permitir una eliminación en cascada (CASCADE) provocaría la pérdida irrecuperable de artículos y sus ventas asociadas. Por otro lado, SET_NULL forzaría a declarar product_type_id como anulable, contraviniendo la regla de negocio de que todo producto debe pertenecer a un tipo obligatoriamente.

Al configurar on_delete=models.RESTRICT, cualquier intento de ejecutar .delete() sobre una instancia de ProductType con productos vinculados provoca que el ORM intercepte la llamada y eleve la excepción runtime django.db.models.deletion.RestrictedError. Esto detiene la ejecución inmediatamente en la capa de aplicación, impidiendo la emisión de sentencias SQL destructivas y manteniendo la base de datos totalmente consistente.


6. Secuencia de Comandos CLI y Migraciones

La persistencia de las definiciones del ORM hacia la base de datos relacional sigue un proceso CLI estandarizado:

  1. Generación del Archivo de Migración:

    python manage.py makemigrations product
    
    Efecto: El motor de migraciones de Django inspecciona apps/product/models.py, detecta las entidades ProductType y Product, y compila el archivo declarativo apps/product/migrations/0001_initial.py.

  2. Aplicación del Esquema Físico:

    python manage.py migrate
    
    Efecto: Transforma las definiciones del archivo de migración en sentencias SQL DDL (CREATE TABLE, ALTER TABLE, ADD CONSTRAINT) y las ejecuta en el motor relacional activo (PostgreSQL, MySQL o SQL Server), registrando la migración en la tabla django_migrations.

Ciclo de Vida del Cambio de Esquema

[Definición de Modelo]
  apps/product/models.py
       │
       ▼
[Detección de Cambios]
  python manage.py makemigrations product
       │
       ▼
[Generación de Archivo SQL / Migración]
  apps/product/migrations/0001_initial.py
       │
       ▼
[Aplicación en BD]
  python manage.py migrate

7. Esquema Físico Resultante de las Tablas

A continuación se describe la estructura física DDL generada en la base de datos relacional tras la ejecución de la migración product.0001_initial.

Tabla: product_types

Nombre de Columna Tipo de Dato (SQL / Django) Clave Permite Null Valor por Defecto Restricciones / Validaciones
id BIGINT / BigAutoField PK NO Auto-increment Clave primaria.
name VARCHAR(100) / CharField UNIQUE NO Ninguno Único. Mínimo 2 caracteres (MinLengthValidator(2)).
description VARCHAR(255) / CharField Ninguna SÍ NULL Campo opcional (blank=True, null=True).
status VARCHAR(8) / CharField Ninguna NO 'inactive' Estado heredado de StoreLabModel.
created_at TIMESTAMP / DATETIME Ninguna NO NOW() Sello de tiempo de creación (auto_now_add).
updated_at TIMESTAMP / DATETIME Ninguna NO NOW() Sello de tiempo de actualización (auto_now).
  • Restricción de Tabla: product_types_status_valid $\to$ CHECK (status IN ('active', 'inactive')).

Tabla: products

Nombre de Columna Tipo de Dato (SQL / Django) Clave Permite Null Valor por Defecto Restricciones / Validaciones
id BIGINT / BigAutoField PK NO Auto-increment Clave primaria.
product_type_id BIGINT / ForeignKey FK NO Ninguno FK apuntando directamente a product_types(id). Restricción ON DELETE RESTRICT e índice explícito generado por Django.
name VARCHAR(120) / CharField UNIQUE NO Ninguno Único. Mínimo 2 caracteres (MinLengthValidator(2)).
description VARCHAR(255) / CharField Ninguna SÍ NULL Campo opcional (blank=True, null=True).
price NUMERIC(12,2) / DecimalField Ninguna NO Ninguno Precisión decimal. Validado con MinValueValidator(Decimal("0")).
stock INTEGER / PositiveIntegerField Ninguna NO 0 Cantidad entera no negativa (stock >= 0).
status VARCHAR(8) / CharField Ninguna NO 'inactive' Estado heredado de StoreLabModel.
created_at TIMESTAMP / DATETIME Ninguna NO NOW() Sello de tiempo de creación (auto_now_add).
updated_at TIMESTAMP / DATETIME Ninguna NO NOW() Sello de tiempo de actualización (auto_now).
  • Restricciones de Tabla:
  • products_status_valid $\to$ CHECK (status IN ('active', 'inactive')).
  • product_price_non_neg $\to$ CHECK (price >= 0).

8. Errores Frecuentes y Malas Prácticas

En la siguiente tabla se resumen las desviaciones técnicas más habituales durante el desarrollo del ISS-05, su impacto en el sistema y la solución arquitectónica requerida:

Error Identificado Impacto Técnico en el Sistema Solución Correcta / Arquitectura Esperada
Usar CASCADE por defecto en la ForeignKey Eliminar un ProductType destruye automáticamente en cascada todos los productos vinculados, borrando inventario activo y corrompiendo la trazabilidad comercial. Declarar explícitamente on_delete=models.RESTRICT en el campo product_type del modelo Product.
Usar FloatField o permitir precios negativos FloatField introduce imprecisiones de coma flotante por IEEE 754. Omitir restricciones permite guardar precios inválidos como -$15.00. Utilizar DecimalField(max_digits=12, decimal_places=2) con MinValueValidator(Decimal("0")) y un CheckConstraint(condition=models.Q(price__gte=0)).
Omitir db_column='product_type_id' Django generará la columna usando la convención por defecto, pero omitir la declaración explicita rompe la estandarización estricta del esquema relacional de StoreLab. Incluir explícitamente db_column="product_type_id" en la definición de la ForeignKey.
Omitir la validación de longitud mínima en el nombre Permite registrar nombres de un solo carácter (por ejemplo, "A"), violando los criterios de calidad de datos del catálogo comercial. Asignar validators=[MinLengthValidator(2)] en la propiedad name de ambos modelos (ProductType y Product).
Agregar campos ajenos al diccionario de StoreLab Añadir atributos como brand, min_stock o quantity contamina el dominio académico e invalida las pruebas automatizadas del proyecto. Restringir los atributos del modelo Product estrictamente a: product_type, name, description, price, stock, y los heredados de StoreLabModel.

9. Criterios de Aceptación (AC-05-01 al AC-05-04)

Para validar técnicamente la entrega del ISS-05, se deben cumplir estrictamente los cuatro Criterios de Aceptación definidos en la especificación oficial:

  • AC-05-01: La clave foránea product_type_id es obligatoria (null=False, blank=False).
  • AC-05-02: El intento de borrar un ProductType que tenga productos asociados debe fallar en la capa de aplicación lanzando la excepción RestrictedError.
  • AC-05-03: El nombre del producto (name) es único en la base de datos (unique=True).
  • AC-05-04: Los valores por defecto al instanciar las entidades sin parámetros explícitos son status = "inactive" y stock = 0.

10. Verificación, Evidencias y Tabla GATE

Pruebas de Verificación Ejecutadas

La validación automatizada se realiza mediante la ejecución de los métodos de prueba pertenecientes a la suite ProductApiTests en apps/product/tests.py:

python manage.py test apps.product.tests.ProductApiTests.test_default_status_and_type_restriction
python manage.py test apps.product.tests.ProductApiTests.test_duplicate_product_name

Evidencias Documentadas

  • EVI-05-01: Generación e inyección exitosa de la migración product.0001_initial en el motor de base de datos relacional.
  • EVI-05-02: Excepción django.db.models.deletion.RestrictedError capturada y confirmada por la suite de pruebas unitarias al ejecutar product_type.delete().

Decisión GATE de Avance (GATE ISS-05)

Criterio de Aceptación (AC) Verificación Aplicada Evidencia Documentada Resultado
AC-05-01 (product_type_id obligatorio) FK sin null EVI-05-01 (Modelo y migración product.0001_initial) PASS
AC-05-02 (Restricción de borrado) delete del tipo EVI-05-02 (Excepción RestrictedError verificada) PASS
AC-05-03 (Nombre de producto único) nombre duplicado = 400 Pruebas unitarias de unicidad en ORM / BD PASS
AC-05-04 (Valores por defecto) defaults Verificación de valores predeterminados (inactive, 0) PASS

11. Cuestionario de Defensa Oral Técnica

Pregunta 1

Pregunta: ¿Por qué se debe utilizar DecimalField en lugar de FloatField para el precio de un producto, y por qué el validador MinValueValidator utiliza Decimal("0") en lugar de 0 o 0.0?

  • Respuesta Técnica: FloatField utiliza la representación binaria de coma flotante IEEE 754, la cual introduce errores imprecisos de redondeo que son inaceptables en aplicaciones contables o comerciales. DecimalField mapea al tipo de dato relacional NUMERIC/DECIMAL, almacenando valores de punto fijo con precisión exacta. El validador MinValueValidator debe compararse con una instancia explícita de Decimal("0") para evitar que el intérprete de Python realice operaciones de coerción Implícita entre tipos incompatibles (float y Decimal), lo que elevaría un error TypeError o generaría inconsistencias en la evaluación del límite.
  • Justificación Pedagógica: Evalúa la comprensión del estudiante sobre la precisión numérica en sistemas financieros y la prevención de errores de conversión en el ORM de Django.

Pregunta 2

Pregunta: ¿Qué ocurre a nivel de base de datos relacional y de aplicación al intentar eliminar un ProductType que tiene productos asociados, si la relación está configurada con on_delete=models.RESTRICT?

  • Respuesta Técnica: Al invocar product_type.delete(), Django intercepta la llamada en la capa de aplicación antes de enviar la orden SQL a la base de datos. El ORM consulta si existen registros en la tabla products referenciando el id del ProductType. Al encontrar registros asociados, Django aborta la operación y eleva inmediatamente la excepción django.db.models.deletion.RestrictedError. No se ejecuta ninguna sentencia SQL DELETE, impidiendo el borrado y conservando intacta la base de datos.
  • Justificación Pedagógica: Verifica la capacidad del estudiante para diferenciar entre el manejo de excepciones en la capa de aplicación (ORM) y la ejecución de restricciones de integridad en el motor SQL.

Pregunta 3

Pregunta: ¿Cuál es la función técnica del parámetro db_column="product_type_id" en la clave foránea del modelo Product?

  • Respuesta Técnica: Por defecto, Django infiere el nombre de la columna física agregando el sufijo _id al nombre del atributo en la clase (product_type $\to$ product_type_id). Declarar db_column="product_type_id" fija el nombre de la columna en la tabla de la base de datos de manera explícita y explícitamente determinista. Esto garantiza el cumplimiento estricto del esquema DDL normalizado de StoreLab, desacoplando el nombre del atributo en Python de la columna en la base de datos.
  • Justificación Pedagógica: Evalúa la habilidad del estudiante para controlar y estandarizar el esquema físico relacional sin depender de las convenciones implícitas del framework.

Pregunta 4

Pregunta: ¿Qué diferencia existe entre MinValueValidator(Decimal("0")) y CheckConstraint(condition=models.Q(price__gte=0)) aplicados sobre el campo price?

  • Respuesta Técnica: Corresponden a un patrón de defensa en profundidad. MinValueValidator se ejecuta en la capa de aplicación (entorno Python) cuando se invoca la validación del modelo (full_clean()) o dentro de un Serializer/Formulario, rechazando datos inválidos antes de abrir una conexión a la base de datos. Por el contrario, CheckConstraint se traduce en una regla DDL física en la tabla (CHECK (price >= 0)). Esta última actúa como la barrera final que protege la base de datos ante inserciones directas por SQL, scripts administrativos o desvíos del ORM.
  • Justificación Pedagógica: Mide la visión arquitectónica del estudiante para diseñar controles de calidad de datos multinivel (aplicación vs. motor relacional).

Pregunta 5

Pregunta: ¿Por qué la clase Product define la lista ordering = ["name", "id"] dentro de su clase interna Meta?

  • Respuesta Técnica: Garantiza un ordenamiento estricto y determinista en las consultas SELECT ejecutadas por el ORM. Ordenar únicamente por name puede producir resultados no deterministas (non-deterministic ordering) si existieran nombres idénticos en conjuntos no únicos o durante la paginación a nivel de base de datos relacional. La inclusión de id actúa como una clave de desempate secundaria y única, evitando que los registros cambien aleatoriamente de posición entre páginas cuando se exponen servicios API paginados.
  • Justificación Pedagógica: Valida la preparación del estudiante para construir APIs REST paginables, deterministas y seguras para el consumo de clientes web y móviles.

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