📚 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.
🎬 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
djangorestframework==3.17.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 pruebasAPIClienty los mecanismos de respuesta HTTP. -
Impacto de Omisión: La importación
from rest_framework import serializersfallaría con un errorModuleNotFoundError. -
djangorestframework-camel-case==1.4.2: - Responsabilidad: Suministra la capa de conversión de nombres mediante las clases
CamelCaseJSONRendereryCamelCaseJSONParser. -
Impacto de Omisión: Las claves del JSON transmitido por la red mantendrían la sintaxis
snake_casenativa de Python, rompiendo el contrato de interfaz esperado por aplicaciones cliente construidas en ecosistemas JavaScript/TypeScript. -
django-cors-headers==4.9.0: - Responsabilidad: Proporciona el middleware
corsheaders.middleware.CorsMiddlewarepara inyectar de manera automática las cabeceras HTTP de intercambio de recursos de origen cruzado (Cross-Origin Resource Sharing). - 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ónfields = "__all__". Se definen comoread_only_fieldslas columnas gestionadas automáticamente por el servidor (id,created_at,updated_at). Enextra_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 recibeNone, una cadena vacía""o espacios en blanco" ", retorna explícitamenteNone. Esta conversión es crítica: en bases de datos relacionales con restriccionesunique=True, insertar múltiples cadenas vacías ("") provoca una violación de unicidad (IntegrityError). En cambio, el estándar SQL trata los valoresNULL(Noneen 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 entradaaddressyphonegarantizando que las cadenas vacías enviadas desde el frontend se traduzcan de manera consistente aNone(NULLen 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 haciaNone. - 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ícitamentesource="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 deProductType.objects.all()y puebla la clavevalidated_data['product_type']con la instancia real del modelo ORMProductType(no solo un entero). Esto permite que al invocarProduct.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) yproductTypeName(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 derivadaproduct_name(expuesta en la red comoproductName), marcando la totalidad de los campos comoread_only_fields.- Anidamiento en
SaleSerializer: Utilizalines = ProductSaleSerializer(many=True, read_only=True)para incrustar el arreglo de líneas asociadas a la transacción yclient_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
response.data:APIClientprocesa 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 decamelCasehaciasnake_casepara facilitar la inspección con código Python (response.data["created_at"]). Por consiguiente, evaluarresponse.datavalora los datos internos, pero no garantiza que el cable HTTP esté enviandocreatedAt.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 mediantejson.loads(response.content)y verificar la presencia explícita de la clave encamelCase.
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 internaMeta. - 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.pycomocreated_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
CorsMiddlewaredeMIDDLEWAREo situarlo por debajo deSecurityMiddlewareoCommonMiddleware. - Riesgo TÉCNICO: Si no se ubica como el primer middleware, las peticiones preflight
OPTIONSenviadas 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 restriccionesunique=Trueynull=True(comoemailenClient). - 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 envalidate_emailovalidatepara retornarNone(NULLen 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 campoproductTypeName, 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:
Registro de Evidencia Documental (EVI-07-01)
EVI-07-01: Ejecución de la aserción directa sobreresponse.contentdentro de la pruebaClientApiTests.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