Saltar a contenido

📚 Unidad ISS-07 · Serializers y JSON camelCase — capa 🧠 APRENDER

🛠 Construir este bloque → · ✅ Condiciones de cierre (GATE)

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

🖥 Presentación del ISS

Presentación de la unidad. Diapositivas de ISS-07 — Serializers y JSON camelCase (12 diapositivas). Se visualiza aquí, dentro del sitio.

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

12 diapositivas · se visualiza dentro del sitio.

🎬 Video explicativo

Recorrido audiovisual de la unidad. El video recorre el ISS técnico de ISS-07 — Serializers y JSON camelCase, bloque por bloque.

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

Infografía

Mapa conceptual

  • DRF y Paquetes
  • djangorestframework (3.17.2): Aporta serializers, APIView, ModelViewSet y cliente de pruebas
  • djangorestframework-camel-case (1.4.2): Convierte created_at en createdAt y viceversa
  • django-cors-headers (4.9.0): Inserta CorsMiddleware para permitir peticiones de otros orígenes
  • Settings Config
  • INSTALLED_APPS: Se agregan rest_framework y corsheaders
  • MIDDLEWARE: Se inserta CorsMiddleware
  • REST_FRAMEWORK
    • DEFAULT_PERMISSION_CLASSES: AllowAny
    • DEFAULT_RENDERER_CLASSES: CamelCaseJSONRenderer
    • DEFAULT_PARSER_CLASSES: CamelCaseJSONParser
    • DEFAULT_PAGINATION_CLASS: None
  • ClientSerializer
  • Modelo Asociado: Client
  • Campos: id, name, address, phone, email, status, created_at, updated_at
  • validate_email: Normaliza a minúsculas y convierte blanco en None
  • ProductSerializers
  • ProductTypeSerializer: Gestiona tipos y normaliza descripciones vacías a None
  • ProductSerializer
    • product_type_id: PrimaryKeyRelatedField escribible
    • product_type_name: CharField de solo lectura
  • SaleSerializers
  • ProductSaleSerializer: Muestra líneas y product_name de solo lectura
  • SaleSerializer: Expone cabecera, client_name y líneas anidadas
  • Transfomación JSON camelCase
  • Flujo de Salida: Python created_at -> Tabla created_at -> JSON createdAt
  • Parser: Realiza el camino inverso en las peticiones entrantes
  • Criterios de Aceptación
  • AC-07-01: Cuerpo HTTP contiene createdAt y no created_at
  • AC-07-02: Correos como Maria@Example.com se guardan en minúsculas
  • AC-07-03: Producto informa productTypeName sin segundo query
  • GATE
  • Evidencia EVI-07-01: Aserción sobre response.content exitosa (PASS)

Guía de Estudio Exhaustiva: Serializers y JSON camelCase en Django REST Framework (ISS-07)


1. Objetivos y Requisitos de ISS-07

En el diseño de arquitecturas de software orientadas a servicios y microservicios, la capa de serialización constituye el contrato de interfaz crítico que vincula el dominio interno del sistema (los modelos del ORM en Python y Django) con las representaciones externas consumidas por clientes heterogéneos (aplicaciones web en SPA, móviles o sistemas de terceros). Esta capa asume la responsabilidad estratégica de traducir, validar y formatear los flujos de información bidireccionales, garantizando que el contrato de red expuesto hacia el exterior cumpla de manera estricta con las convenciones cliente, al tiempo que preserva la integridad estructural y las reglas sintácticas del servidor.

El objetivo principal de la unidad ISS-07 es implementar la transformación transparente de los modelos del ORM de Django hacia representaciones JSON en formato camelCase (por ejemplo, createdAt o productTypeId) durante el intercambio de cargas útiles en la red HTTP, sin alterar en ningún momento las convenciones idiomáticas de nomenclatura snake_case de los atributos en Python (como created_at o product_type_id) ni de los campos y tablas en la base de datos relacional.

Requisitos Previos Necesarios

Para abordar la implementación técnica de esta unidad, se deben cumplir rigurosamente los siguientes prerrequisitos del entorno: 1. Superación Formal de ISS-06: Disponer del esquema de base de datos relacional completamente estructurado y migrado con las entidades de negocio (Client, ProductType, Product, Sale y ProductSale). 2. Terminal Activa en Entorno Virtual Isolado (.venv): Garantizar que el entorno virtual esté activado mediante el comando source .venv/bin/activate, verificando que la invocación del intérprete (which python) apunte al ejecutable aislado en .venv/bin/python.

Impacto Arquitectónico

Desacoplar de forma estricta las convenciones sintácticas del cliente HTTP (que requiere JSON con claves en camelCase) respecto a las del backend (que opera con objetos Python y modelos del ORM estructurados en snake_case bajo la norma PEP 8) garantiza un aislamiento de capas de alta pureza. Este desacoplamiento impide que las decisiones de interfaz o requerimientos de renderizado de clientes JavaScript/TypeScript contaminen el modelo de dominio interno o el diseño físico de las tablas en la base de datos relacional.

A continuación, se establece el marco conceptual y el vocabulario técnico especializado necesario para dominar los componentes de serialización de DRF.


2. Conceptos Esenciales y Vocabulario Técnico

El dominio y manejo fluido de la terminología técnica resulta indispensable para garantizar una comunicación precisa dentro de equipos de ingeniería de software, así como para la sustentación y defensa técnica rigurosa de las decisiones de arquitectura ante un comité evaluador.

Término Técnico Propósito y Funcionalidad Exacta en ISS-07
Django REST Framework (DRF) Marco de trabajo (toolkit) para Django que aporta la infraestructura de serialización, vistas, renderizadores, parsers y utilidades de testing para construir APIs RESTful profesionales.
Serializers y ModelSerializer Componentes de DRF encargados de convertir datos complejos (instancias de modelos) en tipos de datos nativos de Python (y viceversa), gestionando la validación de entrada y la abstracción de campos basada en el ORM.
djangorestframework-camel-case Librería de terceros que intercepta el ciclo de vida de renderizado y parseo de DRF para transformar dinámicamente las claves del JSON de snake_case a camelCase en respuestas HTTP salientes, y de camelCase a snake_case en peticiones entrantes.
CorsMiddleware Middleware provisto por django-cors-headers que intercepta las peticiones HTTP para inyectar las cabeceras CORS de origen cruzado (Access-Control-Allow-Origin), evitando bloqueos de seguridad en navegadores web.
CamelCaseJSONRenderer y CamelCaseJSONParser Clases de renderizado y parseo de djangorestframework-camel-case configuradas globalmente en DRF para transformar los payloads transmitidos en la red HTTP a formato camelCase.
PrimaryKeyRelatedField Campo de serializer para representar relaciones de clave foránea (ForeignKey) mediante su clave primaria (id), gestionando la vinculación e instanciación directa con modelos relacionados.
validate_email y Normalización Método de validación a nivel de campo en un serializer que intercepta la entrada del correo para convertir cadenas vacías ("") en None y normalizar el texto a minúsculas.
response.content vs response.data Distinción crítica en testing: response.data contiene el diccionario nativo de Python procesado por APIClient (re-parseado a snake_case), mientras que response.content contiene los bytes/string JSON puros del alambre HTTP (en camelCase).

Una vez cimentada la base conceptual, se procede con la gestión de dependencias y la instalación de paquetes mediante el intérprete del entorno activo.


3. Dependencias del Proyecto e Instalación mediante CLI

La gestión estricta de dependencias congeladas y el aislamiento del entorno de ejecución mediante .venv constituyen prácticas fundamentales de la ingeniería de software para asegurar la reproducibilidad del entorno de producción y desarrollo.

Comandos de Instalación CLI

La incorporación de los paquetes necesarios para habilitar la API REST, el soporte de CORS y la transformación sintáctica a camelCase se ejecuta utilizando explícitamente el módulo pip del intérprete del entorno activo:

source .venv/bin/activate
python -m pip install "djangorestframework==3.17.2" "djangorestframework-camel-case==1.4.2" "django-cors-headers==4.9.0"
python -m pip freeze > requirements.txt

Responsabilidad Técnica Individual de los Paquetes

  1. djangorestframework==3.17.2:
  2. Responsabilidad: Aporta la arquitectura core para la construcción de la API RESTful. Proporciona las clases base serializers.ModelSerializer, serializers.Serializer, el cliente de pruebas APIClient y los mecanismos de respuesta HTTP.
  3. Impacto de Omisión: La importación from rest_framework import serializers fallaría con un error ModuleNotFoundError.

  4. djangorestframework-camel-case==1.4.2:

  5. Responsabilidad: Suministra la capa de conversión de nombres mediante las clases CamelCaseJSONRenderer y CamelCaseJSONParser.
  6. Impacto de Omisión: Las claves del JSON transmitido por la red mantendrían la sintaxis snake_case nativa de Python, rompiendo el contrato de interfaz esperado por aplicaciones cliente construidas en ecosistemas JavaScript/TypeScript.

  7. django-cors-headers==4.9.0:

  8. Responsabilidad: Proporciona el middleware corsheaders.middleware.CorsMiddleware para inyectar de manera automática las cabeceras HTTP de intercambio de recursos de origen cruzado (Cross-Origin Resource Sharing).
  9. Impacto de Omisión: Los navegadores web aplicarán la política de mismo origen (Same-Origin Policy), bloqueando las solicitudes HTTP provenientes de clientes web servidos desde otros dominios o puertos.

Con las dependencias instaladas y fijadas en requirements.txt, se prosigue con la configuración global del sistema en el paquete settings.


4. Configuración del Entorno de Aplicación (config/settings/__init__.py)

Los ajustes globales centralizados en config/settings/__init__.py actúan como el núcleo de orquestación que gobierna el pipeline de procesamiento de peticiones HTTP, la pila de middlewares, el comportamiento de renderizado y el parseo de payloads.

Intervenciones Clave en la Configuración del Sistema

El parche aplicado sobre config/settings/__init__.py estructura las declaraciones necesarias divididas en cuatro bloques fundamentales:

# 1. Registro de aplicaciones en INSTALLED_APPS
INSTALLED_APPS = [
    # ... aplicaciones core de Django ...
    "rest_framework",
    "corsheaders",
    "apps.security.apps.SecurityConfig",
    "apps.client.apps.ClientConfig",
    "apps.product.apps.ProductConfig",
    "apps.sale.apps.SaleConfig",
]

# 2. Inserción estratégica de CorsMiddleware en MIDDLEWARE
MIDDLEWARE = [
    "corsheaders.middleware.CorsMiddleware",  # UBICACIÓN CRÍTICA: Debe ser el primer 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",
]

# 3. Configuración de reglas CORS desde variables de entorno
CORS_ALLOW_ALL_ORIGINS = os.environ.get("CORS_ALLOW_ALL_ORIGINS", "True").strip().lower() in {
    "1",
    "true",
    "yes",
    "on",
}

# 4. Configuración global de Django REST Framework
REST_FRAMEWORK = {
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework.permissions.AllowAny",
    ],
    "DEFAULT_RENDERER_CLASSES": [
        "djangorestframework_camel_case.render.CamelCaseJSONRenderer",
        "djangorestframework_camel_case.render.CamelCaseBrowsableAPIRenderer",
    ],
    "DEFAULT_PARSER_CLASSES": [
        "djangorestframework_camel_case.parser.CamelCaseJSONParser",
        "djangorestframework_camel_case.parser.CamelCaseFormParser",
        "djangorestframework_camel_case.parser.CamelCaseMultiPartParser",
    ],
    "DEFAULT_PAGINATION_CLASS": None,
}

Análisis sobre la Ubicación Estrictamente Prioritaria de CorsMiddleware

Es de vital importancia observar la ubicación de "corsheaders.middleware.CorsMiddleware" como la primera entrada del arreglo MIDDLEWARE. Esta posición garantiza que el middleware intercepte la solicitud HTTP entrante antes de que SecurityMiddleware o CommonMiddleware inicien el procesamiento. Cuando un navegador web realiza una petición de origen cruzado mediante un preflight HTTP OPTIONS, CorsMiddleware debe adjuntar inmediatamente las cabeceras Access-Control-Allow-Origin y responder con éxito. Si se ubica más abajo en la pila, las comprobaciones de seguridad previas bloquearán la petición preflight antes de que las cabeceras CORS puedan ser agregadas, interrumpiendo la comunicación del cliente.

Definida la infraestructura global de la API, se aborda la implementación técnica de los componentes de serialización por cada aplicación de negocio.


5. Análisis y Explicación por Bloques Semánticos de Serializers

Los serializers en DRF actúan como transformadores bidireccionales y guardianes de la integridad de datos. En el flujo saliente, transforman objetos complejos del ORM en estructuras nativas de Python que el renderizador convierte en JSON. En el flujo entrante, reciben datos primitivos, los someten a reglas de validación semántica y estructural, y producen diccionarios validados aptos para la persistencia.

5.1 ClientSerializer (apps/client/serializers.py)

El archivo apps/client/serializers.py implementa la serialización y las reglas de validación para la gestión de compradores.

from rest_framework import serializers
from apps.client.models import Client

class ClientSerializer(serializers.ModelSerializer):
    class Meta:
        model = Client
        fields = [
            "id",
            "name",
            "address",
            "phone",
            "email",
            "status",
            "created_at",
            "updated_at",
        ]
        read_only_fields = ["id", "created_at", "updated_at"]
        extra_kwargs = {
            "name": {"min_length": 2, "max_length": 150},
            "email": {"required": False, "allow_null": True, "allow_blank": True},
            "address": {"required": False, "allow_null": True, "allow_blank": True},
            "phone": {"required": False, "allow_null": True, "allow_blank": True},
        }

    def validate_email(self, value):
        if value is None or str(value).strip() == "":
            return None
        return value.strip().lower()

    def validate(self, attrs):
        for field in ("address", "phone"):
            if attrs.get(field) == "":
                attrs[field] = None
        return attrs
Análisis por Bloques Semánticos:
  • Clase Interna Meta: Especifica el modelo base (Client) y declara explícitamente la tupla de campos expuestos. Se excluye conscientemente el uso del antipatrón fields = "__all__". Se definen como read_only_fields las columnas gestionadas automáticamente por el servidor (id, created_at, updated_at). En extra_kwargs, se flexibiliza la recepción de valores nulos o en blanco para campos opcionales.
  • Método validate_email(self, value):
  • Lógica de Integridad de Base de Datos: Intercepta el campo email. Si recibe None, una cadena vacía "" o espacios en blanco " ", retorna explícitamente None. Esta conversión es crítica: en bases de datos relacionales con restricciones unique=True, insertar múltiples cadenas vacías ("") provoca una violación de unicidad (IntegrityError). En cambio, el estándar SQL trata los valores NULL (None en Python) como no colisionantes entre sí.
  • Normalización: Si el correo contiene texto válido, aplica .strip().lower() para almacenar de forma estandarizada cadenas en minúsculas (ej. "Maria@Example.com" $\rightarrow$ "maria@example.com").
  • Método Global validate(self, attrs): Recorre los atributos de entrada address y phone garantizando que las cadenas vacías enviadas desde el frontend se traduzcan de manera consistente a None (NULL en SQL).

5.2 ProductTypeSerializer y ProductSerializer (apps/product/serializers.py)

El archivo apps/product/serializers.py administra la clasificación y el catálogo de productos.

from rest_framework import serializers
from apps.product.models import Product, ProductType

class ProductTypeSerializer(serializers.ModelSerializer):
    class Meta:
        model = ProductType
        fields = ["id", "name", "description", "status", "created_at", "updated_at"]
        read_only_fields = ["id", "created_at", "updated_at"]
        extra_kwargs = {
            "name": {"min_length": 2, "max_length": 100},
            "description": {"required": False, "allow_null": True, "allow_blank": True},
        }

    def validate_description(self, value):
        if value is None or str(value).strip() == "":
            return None
        return value

class ProductSerializer(serializers.ModelSerializer):
    product_type_id = serializers.PrimaryKeyRelatedField(
        source="product_type",
        queryset=ProductType.objects.all(),
    )
    product_type_name = serializers.CharField(source="product_type.name", read_only=True)

    class Meta:
        model = Product
        fields = [
            "id",
            "product_type_id",
            "product_type_name",
            "name",
            "description",
            "price",
            "stock",
            "status",
            "created_at",
            "updated_at",
        ]
        read_only_fields = ["id", "product_type_name", "created_at", "updated_at"]
        extra_kwargs = {
            "name": {"min_length": 2, "max_length": 120},
            "description": {"required": False, "allow_null": True, "allow_blank": True},
        }

    def validate_description(self, value):
        if value is None or str(value).strip() == "":
            return None
        return value
Análisis Estructural y Mecanismo Interno del ORM:
  • Estructura de ProductTypeSerializer: Proporciona el CRUD para las categorías, limpiando la descripción vacía hacia None.
  • Desglose Explicativo de ProductSerializer:
  • product_type_id = serializers.PrimaryKeyRelatedField(source="product_type", queryset=ProductType.objects.all()): Este campo habilita la escritura de la relación de clave foránea. Al declarar explícitamente source="product_type", ocurre una magia interna en DRF: cuando el cliente envía un ID entero por la red HTTP (ej. 3), DRF valida su existencia dentro de ProductType.objects.all() y puebla la clave validated_data['product_type'] con la instancia real del modelo ORM ProductType (no solo un entero). Esto permite que al invocar Product.objects.create(**validated_data), el ORM reciba directamente la instancia relacional requerida.
  • product_type_name = serializers.CharField(source="product_type.name", read_only=True): Campo informativo de solo lectura que navega la relación ORM mediante la notación de punto para exponer el nombre de la categoría en las respuestas de lectura.
  • Transformación en el Borde de Red: El renderizador camelCase traduce en el cable HTTP estos atributos hacia productTypeId (en escrituras) y productTypeName (en lecturas).

5.3 ProductSaleSerializer y SaleSerializer (apps/sale/serializers.py)

El archivo apps/sale/serializers.py estructura las representaciones de lectura para las ventas y sus líneas de detalle.

from rest_framework import serializers
from apps.sale.models import ProductSale, Sale

class ProductSaleSerializer(serializers.ModelSerializer):
    product_name = serializers.CharField(source="product.name", read_only=True)

    class Meta:
        model = ProductSale
        fields = [
            "id",
            "product",
            "product_name",
            "quantity",
            "unit_price",
            "line_total",
            "status",
            "created_at",
            "updated_at",
        ]
        read_only_fields = fields

class SaleSerializer(serializers.ModelSerializer):
    client_name = serializers.CharField(source="client.name", read_only=True)
    lines = ProductSaleSerializer(many=True, read_only=True)

    class Meta:
        model = Sale
        fields = [
            "id",
            "client",
            "client_name",
            "sale_date",
            "subtotal",
            "tax",
            "discounts",
            "total",
            "status",
            "lines",
            "created_at",
            "updated_at",
        ]
        read_only_fields = fields
Justificación de Arquitectura de Lectura:
  • ProductSaleSerializer: Define el detalle de venta incorporando la propiedad derivada product_name (expuesta en la red como productName), marcando la totalidad de los campos como read_only_fields.
  • Anidamiento en SaleSerializer: Utiliza lines = ProductSaleSerializer(many=True, read_only=True) para incrustar el arreglo de líneas asociadas a la transacción y client_name = CharField(source="client.name", read_only=True) para el comprador.
  • Propósito: Permite la inspección completa de la cabecera y el detalle de la transacción en una sola consulta HTTP sin mutar el modelo en esta unidad, preservando la inmutabilidad de las transacciones históricas.

A continuación, se presenta el análisis gráfico y formal de la transformación de datos en el borde del sistema.


6. Análisis Comparativo de Arquitectura: Transformación de Datos en el Borde HTTP

El diseño de arquitectura limpia exige que el núcleo de dominio mantenga una total invariabilidad respecto a las formas de representación requeridas por los clientes externos o los motores de almacenamiento persistente.

Diagrama de Pipeline de Transformación de Datos (Flujo Bidireccional)

===================================================================================================
FLUJO SALIENTE (OUTBOUND / RESPUESTA HTTP)
===================================================================================================
 Base de Datos SQL       Modelo ORM Django       Serializer DRF          CamelCaseJSONRenderer   Red HTTP / JSON
┌──────────────────┐    ┌─────────────────┐    ┌─────────────────┐      ┌────────────────────┐  ┌────────────────┐
│ Tabla: clients   │    │ Atributo Python │    │ Diccionario DRF │      │ Interceptor Borde  │  │ Payload Wire   │
│ Columna:         │───>│ Client:         │───>│ Python:         │─────>│ Renderizador HTTP  │─>│ Client JSON:   │
│ created_at       │    │ created_at      │    │ "created_at"    │      │ Transformación     │  │ "createdAt"    │
└──────────────────┘    └─────────────────┘    └─────────────────┘      └────────────────────┘  └────────────────┘

===================================================================================================
FLUJO ENTRANTE (INBOUND / PETICIÓN HTTP)
===================================================================================================
 Red HTTP / JSON         CamelCaseJSONParser     Serializer DRF          Modelo ORM Django       Base de Datos SQL
┌──────────────────┐    ┌─────────────────┐    ┌─────────────────┐      ┌─────────────────┐    ┌──────────────────┐
│ Payload Wire     │    │ Interceptor     │    │ validated_data  │      │ Atributos ORM   │    │ Tabla: products  │
│ Client JSON:     │───>│ Parser HTTP:    │───>│ Python:         │─────>│ Product.objects │───>│ Columna:         │
│ "productTypeId"  │    │ Transformación  │    │ "product_type"  │      │ .create(...)    │    │ product_type_id  │
└──────────────────┘    └─────────────────┘    └─────────────────┘      └─────────────────┘    └──────────────────┘

Tabla Comparativa de Capas del Sistema

Capa / Entorno Convención de Nombres (Ejemplo) Mecanismo de Transformación / Responsable
Base de Datos Relacional clients, product_types, created_at, product_type_id Tablas y columnas físicas gerenciadas por las migraciones relacionales de Django ORM.
Modelos Django (Python) Client.created_at, Product.product_type Atributos de clase en Python adheridos estrictamente a la convención PEP 8 (snake_case).
Borde HTTP / Red (JSON Wire) "createdAt", "productTypeId", "productTypeName" Interceptado y transformado de manera completamente transparente por CamelCaseJSONRenderer y CamelCaseJSONParser.

Demostración Formal de Transparencia

La conversión sintáctica opera de forma completamente aislada en el borde HTTP de DRF. Las operaciones del ORM (Product.objects.filter()), las consultas SQL emitidas a la base de datos y los nombres de las columnas físicas se ejecutan enteramente en snake_case. El renderizador intercepta la salida Python antes de escribir el stream de bytes en la respuesta HTTP, y el parser realiza el proceso inverso antes de pasar el payload al serializer. De este modo, no se altera ninguna regla sintáctica del servidor.

Establecida la arquitectura de transformación, se detalla el comportamiento de los clientes de prueba en DRF.


7. Estrategias de Testing: Aserción sobre response.content vs response.data

Al escribir pruebas de integración para contratos de API REST, surge una distinción crucial entre inspeccionar la propiedad response.data o la propiedad response.content del objeto devuelto por el cliente de pruebas de DRF (APITestCase / APIClient).

                                Respuesta del Servidor HTTP
                                             │
                       ┌─────────────────────┴─────────────────────┐
                       ▼                                           ▼
               response.content                             response.data
       (Cuerpo RAW HTTP en Bytes)                  (Diccionario Python Parseado)
                       │                                           │
             Claves en camelCase:                         Claves en snake_case:
               "createdAt": ...                             "created_at": ...
                       │                                           │
           Prueba REAL del Contrato                      Prueba de Datos Internos
            Transmitido por la Red                        Re-parseado por APIClient

Mecanismo Interno del APIClient de DRF

  1. response.data: APIClient procesa automáticamente el cuerpo de la respuesta haciendo pasar la cadena de bytes de nuevo por el parser configurado (CamelCaseJSONParser). Este parser invierte las claves de camelCase hacia snake_case para facilitar la inspección con código Python (response.data["created_at"]). Por consiguiente, evaluar response.data valora los datos internos, pero no garantiza que el cable HTTP esté enviando createdAt.
  2. response.content: Contiene la cadena de bytes cruda que viajó por la red HTTP. Para realizar una aserción de contrato afirmativa, se debe decodificar el contenido mediante json.loads(response.content) y verificar la presencia explícita de la clave en camelCase.

Fragmento de Código para la Aserción del Contrato de Red

El siguiente test demuestra la verificación afirmativa sobre el cable HTTP:

import json
from rest_framework import status
from rest_framework.test import APITestCase

class ClientContractTests(APITestCase):
    def test_client_wire_contract_camel_case(self):
        response = self.client.post(
            "/api/clients/",
            {"name": "María García", "email": "Maria@Example.com", "phone": "3001234567"},
            format="json",
        )
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

        # 1. Verificación de datos parseados por APIClient (snake_case)
        self.assertEqual(response.data["email"], "maria@example.com")

        # 2. Verificación CONTRACTUAL REAL en la red HTTP (response.content en camelCase)
        wire = json.loads(response.content)
        self.assertIn("createdAt", wire)
        self.assertNotIn("created_at", wire)
        self.assertIn("updatedAt", wire)
        self.assertNotIn("updated_at", wire)

Con la estrategia de testing clarificada, se analizan los errores y antipatrones más comunes en la capa de serialización.


8. Análisis de Errores Frecuentes y Antipatrones de Implementación

Durante la construcción de serializers e integración de transformaciones de datos, se deben evitar cuatro errores críticos recurrentes:

1. Definición de fields = "__all__" en ModelSerializer

  • Antipatrón: Declarar fields = "__all__" dentro de la clase interna Meta.
  • Riesgo TÉCNICO: Genera un acoplamiento directo e indeseado entre la base de datos y la API HTTP. Si el modelo añade posteriormente campos sensibles o internos (como hashes o tokens), estos se expondrán automáticamente en la API sin filtrado, vulnerando el principio de seguridad por diseño.

2. Renombrar Atributos Directamente en los Modelos Django a camelCase

  • Antipatrón: Definir los campos de las clases en models.py como created_At = models.DateTimeField().
  • Riesgo TÉCNICO: Viola la convención de estilo PEP 8 del lenguaje Python y corrompe los esquemas relacionales generados por el ORM. La transformación sintáctica debe ocurrir exclusivamente en la frontera HTTP a través de Renderers/Parsers.

3. Omisión o Posicionamiento Incorrecto de CorsMiddleware

  • Antipatrón: Omitir CorsMiddleware de MIDDLEWARE o situarlo por debajo de SecurityMiddleware o CommonMiddleware.
  • Riesgo TÉCNICO: Si no se ubica como el primer middleware, las peticiones preflight OPTIONS enviadas por los navegadores web serán procesadas o rechazadas por middlewares de seguridad previos sin incorporar las cabeceras CORS (Access-Control-Allow-Origin), bloqueando el acceso al cliente frontend.

4. Fuga de Cadenas Vacías ("") en Campos Únicos con null=True

  • Antipatrón: Permitir que el serializer transmita cadenas vacías ("") a campos del modelo con restricciones unique=True y null=True (como email en Client).
  • Riesgo TÉCNICO: Las bases de datos SQL tratan la cadena vacía "" como un valor escalar concreto. Insertar más de una cadena vacía desencadena un fallo de violación de unicidad (IntegrityError). Se debe interceptar la entrada en validate_email o validate para retornar None (NULL en SQL).

A continuación, se definen los criterios de aceptación formales que norman el desarrollo de la unidad.


9. Criterios de Aceptación (AC-07-01 a AC-07-03)

Los Criterios de Aceptación representan las condiciones funcionales e invariantes técnicas innegociables que debe satisfacer el software desarrollado en ISS-07.

  • AC-07-01: El cuerpo de la respuesta HTTP pura (response.content) devuelto al crear o consultar un cliente contiene explícitamente la clave "createdAt" y no la clave "created_at".
  • AC-07-02: Un correo electrónico suministrado con caracteres en mayúscula o espacios (por ejemplo, "Maria@Example.com") se normaliza y almacena en la base de datos estrictamente como "maria@example.com".
  • AC-07-03: El serializer de productos (ProductSerializer) informa en la respuesta de lectura el nombre descriptivo de la categoría en el campo productTypeName, sin requerir peticiones HTTP adicionales por parte del cliente.

Para certificar estos criterios de aceptación de manera objetiva, se establece el protocolo de verificación y la matriz GATE.


10. Protocolo de Verificación, Evidencias (EVI-07-01) y Matriz GATE

Desde la perspectiva de la evaluación técnica basada en rúbricas universitarias, la Matriz GATE actúe como el filtro de calidad innegociable que audita las evidencias objetivas generadas por el sistema.

Comando de Ejecución del Protocolo de Verificación

La suite de pruebas para certificar la serialización y el comportamiento de la API se invoca ejecutando el siguiente comando CLI desde la raíz del proyecto:

python manage.py test apps.client apps.product

Registro de Evidencia Documental (EVI-07-01)

  • EVI-07-01: Ejecución de la aserción directa sobre response.content dentro de la prueba ClientApiTests.test_create_defaults_and_persists, registrando el resultado exitoso (PASS) al confirmar la presencia de "createdAt" y la ausencia de "created_at".

Matriz GATE de Evaluación Técnica

Criterio de Aceptación Verificación Objetiva Evidencia Técnica Resultado
AC-07-01 Aserción sobre el cuerpo JSON camelCase mediante inspección de response.content. EVI-07-01 PASS
AC-07-02 Comprobación de la normalización del correo en la suite ClientApiTests. Test de cliente (test_create_defaults_and_persists) PASS
AC-07-03 Verificación de exposición del campo derivado productTypeName en ProductApiTests. Test de producto (test_product_crud_and_available_filter) PASS

Como fase final de la guía, se expone el cuestionario analítico diseñado para la preparación de defensas técnicas orales.


11. Cuestionario de Preguntas de Defensa Oral Técnica y Evaluación Formativa

Pregunta 1: ¿Por qué la transformación a camelCase se delega a djangorestframework-camel-case en el borde HTTP en lugar de modificar los campos del modelo en Django?

Respuesta Modelo: Delegar la transformación al borde HTTP cumple rigurosamente con el principio de separación de responsabilidades y la invariabilidad del dominio. Los modelos de Django deben apegarse a las convenciones de Python (PEP 8) y de las bases de datos relacionales, utilizando snake_case (ej. created_at). Modificar las clases del ORM a camelCase corrompería el diseño de la base de datos y violaría los estándares del lenguaje. djangorestframework-camel-case actúa como una capa de traducción en los renderizadores y parsers de DRF, convirtiendo la salida a camelCase y la entrada a snake_case de forma transparente para los clientes web/móviles sin impactar el núcleo del servidor.

Pregunta 2: ¿Qué diferencia fundamental existe entre inspeccionar response.data e inspeccionar response.content durante un test de contrato de API en DRF?

Respuesta Modelo: response.data es una estructura de datos nativa de Python procesada internamente por el cliente de pruebas de DRF (APIClient), el cual vuelve a pasar el cuerpo de la respuesta por el parser configurado (CamelCaseJSONParser), reconvirtiendo las claves a snake_case (como created_at) por ergonomía de código. Por el contrario, response.content almacena la cadena de bytes/JSON pura tal como viajó por la red HTTP. Para validar contractualmente que la API realmente emite camelCase en el alambre, se debe desentramar json.loads(response.content) y afirmar la presencia de claves como "createdAt" y la ausencia de "created_at".

Pregunta 3: ¿Cómo resuelve ClientSerializer el problema de la restricción de unicidad (unique=True) cuando un cliente envía cadenas vacías en campos opcionales como email, teléfono o dirección?

Respuesta Modelo: En bases de datos SQL, una cadena vacía "" es un valor escalar concreto. Insertar múltiples registros con "" provoca un fallo de violación de unicidad (IntegrityError). ClientSerializer resuelve este problema implementando métodos de validación (validate_email y validate) que detectan si el valor recibido es None, "" o espacios en blanco, convirtiendo dicho valor explícitamente a None. En SQL, None se traduce como NULL, un valor que no colisiona con otros valores NULL bajo la restricción de unicidad, salvaguardando la integridad de la base de datos.

Pregunta 4: ¿Cuál es el propósito explícito de utilizar PrimaryKeyRelatedField(source="product_type", ...) combinado con CharField(source="product_type.name", read_only=True) en ProductSerializer?

Respuesta Modelo: Esta combinación desacopla las operaciones de escritura y lectura sobre relaciones del ORM. PrimaryKeyRelatedField(source="product_type", ...) intercepta el campo de entrada product_type_id (visto en el alambre como productTypeId), valida la clave primaria y resuelve la instancia de modelo ORM ProductType dentro de validated_data['product_type'], permitiendo la persistencia directa en el ORM. Por su parte, CharField(source="product_type.name", read_only=True) expone el campo derivado product_type_name (visto como productTypeName) navegando la relación para mostrar el nombre descriptivo de la categoría en lecturas, evitando requerir peticiones HTTP adicionales.

Pregunta 5: ¿Qué ocurre a nivel de navegador y red si se instalan los paquetes de DRF y camelCase pero se omite la configuración de CorsMiddleware en settings?

Respuesta Modelo: Si se omite CorsMiddleware (o se ubica en una posición incorrecta dentro de MIDDLEWARE), las peticiones HTTP de origen cruzado realizadas desde clientes frontend (ej. un cliente web en Angular o React consumiendo la API desde un puerto distinto) serán bloqueadas por la política de mismo origen (Same-Origin Policy) del navegador web. Específicamente, en las solicitudes preflight OPTIONS, el servidor no adjuntará la cabecera Access-Control-Allow-Origin, provocando que el navegador aborte la petición antes de que alcance la capa de serializers o vistas.


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