
Published on :Aug 26, 2025
Guía de Preparación: Entrevista Full Stack (Angular + Python)
Industrias de Enfoque: Pagos en Línea 💳 y Turismo ✈️
Parte 1: Fundamentos de Frontend con Angular
El objetivo es demostrar que puedes construir interfaces de usuario seguras, reactivas y centradas en el usuario, capaces de manejar flujos complejos como transacciones financieras o búsquedas dinámicas de viajes.
1. Componentes y Ciclo de Vida
-
Definición Ampliada: Los componentes son la esencia de Angular, piezas de Lego con las que construyes tu interfaz de usuario. Cada componente es una unidad autónoma y reutilizable que encapsula una vista (plantilla HTML), su comportamiento (clase TypeScript) y su apariencia (estilos CSS). El Ciclo de Vida de un componente es una secuencia predefinida de fases por las que pasa desde su creación hasta su destrucción. Angular nos proporciona "ganchos" (métodos como
ngOnInit
) que nos permiten ejecutar nuestra propia lógica en momentos clave de este ciclo. -
Explicación Detallada: Imagina que un componente es un actor preparándose para salir a escena.
constructor
: Es el primero en ejecutarse. Es como el actor recibiendo el guion. Aquí es donde se realiza la Inyección de Dependencias, pero aún no tienes acceso a los datos de entrada (@Input
).ngOnChanges
: Se dispara cuando Angular detecta cambios en las propiedades de entrada (@Input
). Es como si el director le diera nuevas instrucciones al actor antes de que salga a escena.ngOnInit
: Se ejecuta una sola vez, después del primerngOnChanges
. Es el momento perfecto para que el actor prepare todo lo que necesita para su actuación, como cargar datos iniciales de una API.ngAfterViewInit
: Se ejecuta después de que la vista del componente y sus vistas hijas han sido completamente inicializadas. El actor ya está en el escenario y puede interactuar con el decorado.ngOnDestroy
: Se ejecuta justo antes de que Angular destruya el componente. Es crucial para limpiar el escenario: desuscribirse deObservables
, cancelar temporizadores, etc., para prevenir fugas de memoria que ralentizarían la aplicación.
-
Ejemplo Práctico (Sector Turismo):
// hotel-details.component.ts import { Component, OnInit, OnDestroy } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { HotelService } from '../services/hotel.service'; import { Subscription } from 'rxjs'; @Component({ selector: 'app-hotel-details', template: ` <div *ngIf="hotel"> <h1>{{ hotel.name }}</h1> <p>{{ hotel.description }}</p> </div> ` }) export class HotelDetailsComponent implements OnInit, OnDestroy { hotel: any; private routeSub: Subscription; constructor( private route: ActivatedRoute, private hotelService: HotelService ) {} ngOnInit(): void { // En `ngOnInit`, el componente ya está listo para trabajar. // Es el lugar ideal para llamadas a APIs que solo se necesitan una vez. this.routeSub = this.route.params.subscribe(params => { const hotelId = params['id']; this.hotelService.getHotelById(hotelId).subscribe(data => { this.hotel = data; }); }); } ngOnDestroy(): void { // Si no nos desuscribimos, la suscripción a `route.params` seguiría // existiendo en memoria incluso si el usuario navega a otra página, // creando una fuga de memoria. this.routeSub.unsubscribe(); } }
2. Servicios e Inyección de Dependencias (DI)
-
Definición Ampliada: Un Servicio en Angular es una clase con un propósito bien definido, como obtener datos, registrar eventos o realizar cálculos. No está ligado a ninguna vista. La Inyección de Dependencias es el mecanismo que Angular utiliza para crear e "inyectar" instancias de estos servicios en las clases que los necesitan (como los componentes). Se basa en el principio de Inversión de Control, donde una clase no crea sus propias dependencias, sino que las recibe desde fuera.
-
Explicación Detallada: Piensa en un componente como un chef en una cocina. El chef no debe perder tiempo fabricando su propio cuchillo (una dependencia). En su lugar, la cocina (Angular) le proporciona un cuchillo profesional (el servicio) cuando lo necesita. Esto tiene enormes ventajas:
- Desacoplamiento: El chef (componente) no depende de una marca específica de cuchillo (una implementación concreta del servicio). Solo sabe que necesita "algo que corte".
- Reutilización: El mismo servicio de
HttpClient
puede ser usado por docenas de componentes para hablar con una API. - Testabilidad: Al probar al chef, puedes darle un "cuchillo de utilería" (un mock del servicio) para simular diferentes escenarios sin necesidad de una API real.
-
Ejemplo Práctico (Sector Pagos):
// payment.service.ts import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class PaymentService { private apiUrl = '[https://api.mypaymentgateway.com/v1](https://api.mypaymentgateway.com/v1)'; // Angular "inyecta" una instancia de HttpClient a través del constructor. // PaymentService no sabe cómo crear HttpClient, solo lo usa. constructor(private http: HttpClient) {} processPayment(paymentData: any): Observable<any> { return this.http.post(`${this.apiUrl}/payments`, paymentData); } }
3. Formularios Reactivos
-
Definición Ampliada: Es un enfoque de Angular para manejar formularios donde el "modelo" del formulario (los campos, sus valores y sus reglas de validación) se define explícitamente en la clase del componente TypeScript. Esto crea una separación clara entre la lógica del formulario y su plantilla HTML, proporcionando un control granular y síncrono sobre el estado del formulario.
-
Explicación Detallada: A diferencia de los formularios por plantilla (donde la lógica reside principalmente en el HTML), los formularios reactivos son más robustos para escenarios complejos. El "modelo" es la fuente de verdad. Puedes escuchar los cambios en los valores de un campo o del formulario entero a través de
Observables
(valueChanges
), lo que te permite implementar lógicas complejas, como autocompletados o validaciones que dependen de otros campos, de una manera limpia y reactiva. Son más fáciles de probar unitariamente porque la lógica de validación es solo una función en tu clase TypeScript. -
Ejemplo Práctico (Sector Pagos):
// payment-form.component.ts import { Component, OnInit } from '@angular/core'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; @Component({ selector: 'app-payment-form', }) export class PaymentFormComponent implements OnInit { paymentForm: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit(): void { // El 'modelo' del formulario se define aquí, de forma explícita. // Es la fuente de verdad sobre la estructura y validaciones. this.paymentForm = this.fb.group({ cardholderName: ['', Validators.required], cardNumber: ['', [Validators.required, Validators.pattern('^[0-9]{16}$')]], expiryDate: ['', [Validators.required, Validators.pattern('^(0[1-9]|1[0-2])\/?([0-9]{2})$')]], cvv: ['', [Validators.required, Validators.minLength(3), Validators.maxLength(4)]] }); } onSubmit() { if (this.paymentForm.valid) { console.log('Formulario válido, enviando para procesar...'); // this.paymentService.processPayment(this.paymentForm.value); } } }
4. RxJS y Programación Reactiva
-
Definición Ampliada: RxJS (Reactive Extensions for JavaScript) es una librería para componer código asíncrono y basado en eventos usando Observables. Un
Observable
es como un "flujo" (stream) de datos que puede emitir múltiples valores a lo largo del tiempo. Puedes "suscribirte" a este flujo para reaccionar a cada nuevo valor emitido. La programación reactiva es un paradigma centrado en estos flujos de datos y la propagación del cambio. -
Explicación Detallada: Imagina que una petición HTTP es como pedir una pizza. Una Promesa te dice: "Te prometo que en el futuro te entregaré una pizza (o te diré que no hay)". Un Observable es más como una suscripción a un servicio de pizzas: "Te entregaré una pizza cada vez que salga una nueva del horno". Esto lo hace ideal para manejar eventos que ocurren múltiples veces, como pulsaciones de teclas, clics del ratón o mensajes de un WebSocket. RxJS te da "operadores" (funciones como
map
,filter
,debounceTime
) que son como herramientas para manipular ese flujo de pizzas: puedes cortarlas, añadirles ingredientes o decidir esperar un poco antes de recibirlas. -
Ejemplo Práctico (Sector Turismo):
// hotel-search.component.ts import { Component, OnInit } from '@angular/core'; import { FormControl } from '@angular/forms'; import { debounceTime, distinctUntilChanged, switchMap, catchError } from 'rxjs/operators'; import { HotelService } from '../services/hotel.service'; import { of } from 'rxjs'; @Component({ selector: 'app-hotel-search', }) export class HotelSearchComponent implements OnInit { searchControl = new FormControl(); results: any[]; constructor(private hotelService: HotelService) {} ngOnInit(): void { this.searchControl.valueChanges.pipe( // debounceTime: Espera a que el usuario deje de teclear por 500ms. debounceTime(500), // distinctUntilChanged: Emite solo si el texto es diferente al anterior. distinctUntilChanged(), // switchMap: Cancela la búsqueda anterior si llega una nueva, evitando // que una respuesta lenta de la API sobrescriba una más reciente. switchMap(term => this.hotelService.searchHotels(term).pipe( // Es buena práctica manejar errores dentro del flujo interno. catchError(error => { console.error('Error en la búsqueda:', error); return of([]); // Devuelve un array vacío en caso de error. }) )) ).subscribe(hotels => { this.results = hotels; }); } }
Parte 2: Fundamentos de Backend con Python
El objetivo es demostrar que puedes construir APIs seguras, escalables y que manejen lógica de negocio crítica.
1. Frameworks (FastAPI / Django)
-
Definición Ampliada: Un framework web es un conjunto de herramientas, librerías y convenciones que proporcionan una estructura estándar para construir aplicaciones web.
- Django es un framework "con baterías incluidas". Te da casi todo lo que necesitas desde el principio: un potente ORM, un panel de administración autogenerado, sistema de autenticación, etc. Impone una estructura (Model-Template-View) que guía al desarrollador, lo cual es ideal para proyectos grandes y estructurados.
- FastAPI es un micro-framework moderno y de alto rendimiento. No te impone una estructura, dándote total flexibilidad. Sus principales ventajas son su velocidad (gracias al uso de
asyncio
) y su uso de type hints de Python para validar datos automáticamente y generar documentación interactiva (Swagger UI), lo que acelera enormemente el desarrollo de APIs.
-
Explicación Detallada: La elección depende del proyecto. Para una plataforma de pagos, la robustez, seguridad y el panel de administración de Django son enormes ventajas para auditar transacciones y gestionar usuarios. Para una OTA que necesita consultar simultáneamente a 10 proveedores de vuelos diferentes, el rendimiento y las capacidades asíncronas de FastAPI son perfectas para no hacer esperar al usuario.
2. ORM (Object-Relational Mapping)
-
Definición Ampliada: Un ORM es una técnica de programación que actúa como un "traductor" entre el mundo de la programación orientada a objetos (clases, objetos) y el mundo de las bases de datos relacionales (tablas, filas). Permite al desarrollador manipular los datos de la base de datos utilizando el lenguaje de programación que ya conoce (Python), en lugar de escribir consultas SQL.
-
Explicación Detallada: Las bases de datos relacionales y los lenguajes OO piensan de manera diferente (el llamado "desajuste de impedancia objeto-relacional"). Un ORM como el de Django o SQLAlchemy crea un "mapa" que conecta tus clases de Python (
class Transaction
) con tus tablas de la base de datos (transactions
). Esto no solo te ahorra escribir SQL, sino que también te protege contra ataques de inyección SQL y facilita el cambio de motor de base de datos (de SQLite a PostgreSQL) con mínimos cambios en el código. -
Ejemplo Práctico (Sector Pagos con Django):
# payments/models.py from django.db import models import uuid class Transaction(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) amount = models.DecimalField(max_digits=10, decimal_places=2) currency = models.CharField(max_length=3) status = models.CharField(max_length=20, choices=[('pending', 'Pending'), ('success', 'Success'), ('failed', 'Failed')], default='pending') created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Transaction {self.id} - {self.amount} {self.currency} [{self.status}]" # En el código, harías: Transaction.objects.create(amount=100.00, currency='USD') # El ORM se encarga de generar la consulta SQL segura: # INSERT INTO payments_transaction (...) VALUES (...)
3. APIs RESTful
-
Definición Ampliada: REST (Representational State Transfer) no es un protocolo, sino un conjunto de restricciones arquitectónicas para crear servicios web. Una API que sigue estas reglas se considera "RESTful". Las restricciones clave son:
- Interfaz Uniforme: Usar los métodos HTTP (
GET
,POST
,PUT
,DELETE
) para las operaciones estándar y usar URLs para identificar los recursos (ej./hotels/123
). - Cliente-Servidor: La UI (Angular) y el almacenamiento de datos (Python/DB) están separados, evolucionan de forma independiente.
- Sin Estado (Stateless): Cada petición del cliente al servidor debe contener toda la información necesaria para entenderla y procesarla. El servidor no guarda ningún estado de la sesión del cliente entre peticiones.
- Interfaz Uniforme: Usar los métodos HTTP (
-
Explicación Detallada: La naturaleza sin estado es fundamental para la escalabilidad. Si cualquier servidor puede atender cualquier petición porque no depende de una sesión guardada, es muy fácil añadir más servidores para manejar más tráfico. REST es el "lenguaje franco" de la web, permitiendo que un frontend en Angular se comunique de manera predecible con un backend en Python, Java o cualquier otro lenguaje.
-
Ejemplo Práctico (Sector Turismo con FastAPI):
# main.py from fastapi import FastAPI from pydantic import BaseModel from typing import List app = FastAPI() class Booking(BaseModel): hotel_id: int check_in_date: str check_out_date: str guests: int db_bookings = [] # POST a /bookings para CREAR una nueva reserva. @app.post("/bookings", status_code=201) async def create_booking(booking: Booking): db_bookings.append(booking.dict()) return {"message": "Booking created successfully", "booking_details": booking} # GET a /bookings para LEER todas las reservas. @app.get("/bookings") async def get_bookings() -> List[Booking]: return db_bookings
4. Autenticación y Autorización (JWT)
-
Definición Ampliada:
- Autenticación: Es el proceso de verificar la identidad de un usuario. Responde a la pregunta: "¿Quién eres?".
- Autorización: Es el proceso de verificar si un usuario autenticado tiene permiso para acceder a un recurso específico. Responde a la pregunta: "¿Qué tienes permitido hacer?".
- JWT (JSON Web Token): Es un estándar compacto y autónomo para crear "tokens" de acceso. Un JWT consta de tres partes: Header, Payload (que contiene datos del usuario, como su ID y roles) y Signature (que verifica que el token no ha sido manipulado).
-
Explicación Detallada: El flujo es: 1) El usuario envía sus credenciales (email/password). 2) El servidor las valida y genera un JWT firmado con una clave secreta. 3) El servidor devuelve el JWT al cliente. 4) El cliente (Angular) almacena este token y lo envía en la cabecera
Authorization
de cada petición subsecuente. 5) El servidor, en cada petición, verifica la firma del JWT para asegurarse de que es válido y no ha sido alterado, y luego usa la información del payload para autorizar la acción. Esto es sin estado, el servidor no necesita recordar nada.
Parte 3: Paradigmas y Conceptos Fundamentales de Programación
Esta sección ahora cubre no solo los pilares de la POO, sino también los paradigmas de programación más amplios que definen cómo estructuramos y pensamos sobre nuestro código.
1. Paradigmas de Programación: Imperativo vs. Funcional vs. POO
-
Definición Ampliada: Un paradigma de programación es un estilo o "manera de pensar" sobre cómo resolver problemas con código. No son lenguajes, sino los enfoques que los lenguajes pueden adoptar. Los tres más influyentes son:
- Programación Imperativa: Es el paradigma más antiguo y directo. Se enfoca en el "cómo" se debe hacer algo. El código es una secuencia explícita de comandos que manipulan el estado del programa (variables) para alcanzar un resultado. Es como dar instrucciones de una receta, paso a paso: "primero, toma los huevos; segundo, rómpelos en un tazón; tercero, bátelos". C y Pascal son ejemplos clásicos.
- Programación Funcional (FP): Se enfoca en el "qué" se debe hacer, no en el cómo. Trata la computación como la evaluación de funciones matemáticas y evita cambiar el estado y los datos mutables. En lugar de modificar datos, crea nuevos datos a partir de los existentes. Es como describir el resultado deseado: "un omelet es el resultado de batir huevos y cocinarlos". Lisp, Haskell y F# son puramente funcionales, pero características de FP son ahora comunes en Python, JavaScript y Java.
- Programación Orientada a Objetos (POO): También es una forma de programación imperativa, pero organiza el código en "objetos", que agrupan datos (atributos) y los comportamientos que operan sobre esos datos (métodos). Se centra en modelar entidades del mundo real y sus interacciones.
-
Diferencias Clave:
Característica | Programación Imperativa | Programación Funcional (FP) | Programación Orientada a Objetos (POO) |
---|---|---|---|
Unidad Principal | Comandos / Instrucciones | Funciones Puras | Objetos |
Manejo del Estado | El estado es central y mutable. Las variables se modifican constantemente. | El estado es inmutable. Se evita el estado compartido y se crean nuevos datos. | El estado está encapsulado dentro de los objetos y es mutable, pero controlado por sus métodos. |
Flujo de Control | Bucles (for , while ), condicionales (if/else ). | Composición de funciones, recursión, funciones de orden superior (map , filter ). | Los objetos se envían mensajes entre sí (llamadas a métodos). |
Objetivo Principal | Dar una secuencia de pasos explícita. | Minimizar efectos secundarios y construir programas predecibles. | Modelar el mundo real y gestionar la complejidad a través de la encapsulación. |
-
Ejemplo Práctico: "Dada una lista de números, obtener una nueva lista solo con los números pares multiplicados por 2."
-
Enfoque Imperativo (Python):
```python numeros = [1, 2, 3, 4, 5, 6] resultado = [] # 1. Inicia un estado (lista vacía) for numero in numeros: # 2. Bucle explícito (pasos) if numero % 2 == 0: # 3. Condicional nuevo_numero = numero * 2 # 4. Modifica una variable resultado.append(nuevo_numero) # 5. Muta el estado de 'resultado' print(resultado) # [4, 8, 12] ```
-
Enfoque Funcional (Python):
```python numeros = [1, 2, 3, 4, 5, 6] # Se describe el resultado, componiendo funciones. No hay bucles ni estado mutable. resultado = list(map(lambda x: x * 2, filter(lambda x: x % 2 == 0, numeros))) print(resultado) # [4, 8, 12] ```
-
Enfoque POO (Python):
```python class ProcesadorDeListas: def __init__(self, lista_numeros): self._numeros = lista_numeros # El estado está encapsulado def transformar_pares(self): # La lógica de transformación está contenida en un método resultado = [] for numero in self._numeros: if numero % 2 == 0: resultado.append(numero * 2) return resultado procesador = ProcesadorDeListas([1, 2, 3, 4, 5, 6]) # Se crea un objeto resultado = procesador.transformar_pares() # Se le envía un mensaje print(resultado) # [4, 8, 12] ```
-
2. Herencia
-
Definición Ampliada: La herencia es uno de los pilares de la POO. Es un mecanismo que permite a una nueva clase (subclase o clase hija) basarse en una clase existente (superclase o clase padre). La clase hija hereda todos los atributos y métodos de la clase padre, pudiendo añadir nuevos o modificar los existentes. Esto promueve la reutilización de código y crea una relación jerárquica de tipo "es un" (ej. un
Hotel
"es un" tipo deAlojamiento
). -
Pregunta de Entrevista: "En el contexto de una OTA, podrías tener diferentes tipos de alojamientos: hoteles, apartamentos, hostales. ¿Cómo usarías la herencia para modelar esto?"
-
Ejemplo Práctico (Sector Turismo):
class Accommodation: def __init__(self, name, address, max_guests): self.name = name self.address = address self.max_guests = max_guests def get_availability(self): return "Checking availability..." class Hotel(Accommodation): def __init__(self, name, address, max_guests, star_rating): super().__init__(name, address, max_guests) self.star_rating = star_rating def book_room(self): return f"Booking a room at Hotel {self.name}" class Apartment(Accommodation): def __init__(self, name, address, max_guests, has_kitchen): super().__init__(name, address, max_guests) self.has_kitchen = has_kitchen
3. Polimorfismo
-
Definición Ampliada: El polimorfismo (del griego "muchas formas") es la capacidad de que objetos de diferentes clases respondan a la misma llamada de método, cada uno a su manera. Permite que una única interfaz pueda representar diferentes tipos de objetos. Por ejemplo, si tienes un método
realizar_pago()
, puedes pasárselo a un objetoTarjetaDeCredito
o a un objetoPayPal
, y cada uno sabrá cómo procesar el pago correctamente. -
Pregunta de Entrevista: "Imagina un sistema de pagos que acepta Tarjeta de Crédito, PayPal y Transferencia Bancaria. ¿Cómo aplicarías el polimorfismo para procesar un pago sin tener que escribir un
if/else
gigante?" -
Ejemplo Práctico (Sector Pagos):
from abc import ABC, abstractmethod class PaymentMethod(ABC): @abstractmethod def pay(self, amount): pass class CreditCardPayment(PaymentMethod): def pay(self, amount): print(f"Processing ${amount} payment via Credit Card...") class PayPalPayment(PaymentMethod): def pay(self, amount): print(f"Redirecting to PayPal for ${amount} payment...") # Esta función no sabe qué tipo de método de pago es, # solo que puede llamar al método .pay() sobre él. def process_payment(payment_method: PaymentMethod, amount): payment_method.pay(amount) credit_card = CreditCardPayment() paypal = PayPalPayment() process_payment(credit_card, 150.00) process_payment(paypal, 200.00)
4. Encapsulamiento y Abstracción
-
Definición Ampliada:
- Encapsulamiento: Es el principio de agrupar los datos (atributos) y los métodos que operan sobre ellos dentro de un objeto, y de ocultar el estado interno del objeto del mundo exterior. El acceso a los datos se controla a través de una interfaz pública (métodos).
- Abstracción: Es el principio de ocultar los detalles complejos de implementación y exponer solo las características y funcionalidades relevantes de un objeto. Se centra en el "qué" hace un objeto, no en el "cómo" lo hace.
-
Pregunta de Entrevista: "Explica la diferencia entre encapsulamiento y abstracción usando el ejemplo de reservar un vuelo."
-
Respuesta Esperada: "La abstracción sería la interfaz que ve el usuario: un botón de 'Reservar Vuelo'. El usuario no necesita saber cómo el sistema se conecta a la aerolínea, verifica el asiento o procesa el pago; solo necesita saber que al hacer clic, el vuelo se reserva. El encapsulamiento sería el objeto
Booking
en el backend. Este objeto contendría datos comopassenger_details
,flight_id
ypayment_status
, y métodos como_verify_payment()
o_confirm_seat()
. Estos detalles internos estarían ocultos (encapsulados) y solo se expondrían métodos públicos comocreate_booking()
, protegiendo la integridad de los datos."
Parte 4: Patrones de Diseño de Software 🏗️
Los patrones de diseño son soluciones reutilizables y probadas a problemas comunes que ocurren en el diseño de software. No son código específico, sino conceptos o plantillas que se pueden adaptar a tu problema.
1. Patrón Singleton (Creacional)
-
Definición Ampliada: Este patrón restringe la instanciación de una clase a un único objeto. Asegura que solo exista una instancia de la clase en toda la aplicación y proporciona un punto de acceso global a esa instancia.
- Problema: Necesitas gestionar un recurso compartido, como una conexión a base de datos o un objeto de configuración, y quieres evitar que se creen múltiples instancias que podrían llevar a inconsistencias.
- Solución: La clase se hace responsable de su propia creación y ciclo de vida, asegurando que no se cree ninguna otra instancia.
-
Ejemplo Práctico (Sector Turismo):
class GDSConnection: _instance = None def __new__(cls): if not cls._instance: cls._instance = super(GDSConnection, cls).__new__(cls) cls._instance._initialize_connection() return cls._instance def _initialize_connection(self): self.session_id = "xyz-123-abc" def search_flights(self, origin, destination): return f"Buscando vuelos de {origin} a {destination} usando la sesión {self.session_id}" conn1 = GDSConnection() conn2 = GDSConnection() print(conn1 is conn2) # True
2. Patrón Factory (Creacional)
-
Definición Ampliada: El Patrón Factory define una interfaz para crear un objeto, pero deja que las subclases decidan qué clase concreta instanciar. Permite que una clase delegue la responsabilidad de la instanciación a clases hijas.
- Problema: Tu código necesita crear objetos, pero no quieres que esté acoplado a las clases concretas de los objetos que debe crear. Necesitas flexibilidad para añadir nuevos tipos de objetos en el futuro sin modificar el código que los utiliza.
- Solución: Creas una "fábrica" que se encarga de la lógica de creación. El código cliente solo pide a la fábrica el objeto que necesita, sin saber cómo se construye.
-
Ejemplo Práctico (Sector Pagos):
class PaymentProcessorFactory: @staticmethod def get_processor(processor_type: str): if processor_type == 'stripe': return StripeProcessor() if processor_type == 'paypal': return PayPalProcessor() raise ValueError("Procesador no soportado.") # (Las clases StripeProcessor y PayPalProcessor se definieron antes)
3. Patrón Observer (De Comportamiento)
-
Definición Ampliada: Este patrón establece una dependencia de uno a muchos entre objetos, de modo que cuando un objeto (el "sujeto") cambia de estado, todos sus dependientes (los "observadores") son notificados y actualizados automáticamente.
- Problema: Tienes un objeto cuyo estado es de interés para otros objetos, y quieres notificar a esos otros objetos cuando el estado cambia, sin que el objeto principal sepa quiénes son o cuántos son.
- Solución: El sujeto mantiene una lista de observadores. Cuando su estado cambia, simplemente itera sobre la lista y llama a un método de notificación en cada observador.
-
Ejemplo Práctico (Sector Turismo):
class Booking: # Sujeto def __init__(self): self._observers = [] def attach(self, observer): self._observers.append(observer) def _notify(self): for observer in self._observers: observer.update(self) def cancel(self): # Lógica de cancelación... self._notify() class EmailNotifier: # Observador def update(self, booking): print("Enviando email de cancelación...") booking = Booking() notifier = EmailNotifier() booking.attach(notifier) booking.cancel()
4. Patrón Strategy (De Comportamiento)
-
Definición Ampliada: Este patrón define una familia de algoritmos, encapsula cada uno de ellos y los hace intercambiables. Permite que el algoritmo varíe independientemente de los clientes que lo utilizan.
- Problema: Tienes una tarea que se puede realizar de diferentes maneras (diferentes algoritmos) y quieres poder cambiar entre ellos en tiempo de ejecución sin usar un montón de sentencias
if-elif-else
. - Solución: Encapsulas cada algoritmo en su propia clase ("estrategia") que implementa una interfaz común. El objeto principal ("contexto") mantiene una referencia a una de estas estrategias y delega la ejecución de la tarea a ella.
- Problema: Tienes una tarea que se puede realizar de diferentes maneras (diferentes algoritmos) y quieres poder cambiar entre ellos en tiempo de ejecución sin usar un montón de sentencias
-
Ejemplo Práctico (Sector Turismo):
from abc import ABC, abstractmethod class PricingStrategy(ABC): # Interfaz de la estrategia @abstractmethod def calculate_price(self, base_price): pass class NonRefundableRate(PricingStrategy): def calculate_price(self, base_price): return base_price * 0.9 class HotelBookingContext: def __init__(self, base_price, strategy: PricingStrategy): self._strategy = strategy self._base_price = base_price def get_final_price(self): return self._strategy.calculate_price(self._base_price) booking = HotelBookingContext(200, NonRefundableRate()) print(booking.get_final_price()) # 180.0
Parte 5: Arquitecturas de Software Avanzadas 🏛️
Saber construir una API es una cosa, pero saber diseñarla para que sea resiliente, escalable y fácil de mantener a largo plazo es lo que distingue a un desarrollador senior.
1. Arquitectura Limpia (Clean Architecture)
-
Definición Ampliada: Es un modelo de diseño de software que organiza el código en capas concéntricas, como una cebolla. La regla fundamental, la Regla de Dependencia, establece que las dependencias del código fuente solo pueden apuntar hacia adentro. Nada en una capa interna puede saber nada sobre una capa externa. Específicamente, la lógica de negocio y las reglas de la aplicación no deben depender de la UI, la base de datos, el servidor web u otros elementos externos.
-
Explicación Detallada: Las capas son:
- Entidades: El núcleo. Contienen la lógica de negocio más crítica y general. Son las menos propensas a cambiar.
- Casos de Uso: Orquestan el flujo de datos hacia y desde las entidades para cumplir con un objetivo de la aplicación (ej.
RegistrarUsuario
). - Adaptadores de Interfaz: Convierten los datos del formato más conveniente para los casos de uso al formato más conveniente para las agencias externas como la base de datos o la web.
- Frameworks y Drivers: La capa más externa. Contiene la base de datos, el framework web, la UI, etc.
2. Diseño Guiado por el Dominio (DDD - Domain-Driven Design)
-
Definición Ampliada: DDD es un enfoque para el desarrollo de software complejo que se centra en una colaboración profunda entre los desarrolladores y los expertos del negocio para modelar el "dominio" del problema. Dos conceptos clave son:
- Lenguaje Ubicuo: Un lenguaje compartido y riguroso basado en el modelo del dominio, que se utiliza tanto en las conversaciones como en el código.
- Contextos Delimitados (Bounded Contexts): Límites explícitos dentro de los cuales un modelo de dominio particular es consistente y aplicable.
-
Explicación Detallada: En una OTA, el concepto de "Cliente" puede significar cosas diferentes. Para el equipo de Reservas, un cliente es quien tiene una reserva. Para el equipo de Marketing, un cliente es un prospecto con un historial de búsquedas. DDD nos enseña a no forzar un único modelo para "Cliente". En su lugar, creamos dos
Bounded Contexts
:BookingContext
yMarketingContext
, cada uno con su propio modelo deCustomer
, evitando así complejidades innecesarias.
3. Arquitectura de Microservicios
-
Definición Ampliada: Es un estilo arquitectónico que estructura una aplicación como una colección de servicios pequeños, autónomos y débilmente acoplados. Cada servicio es responsable de una capacidad de negocio específica, se comunica a través de APIs bien definidas (generalmente HTTP/REST), tiene su propia base de datos y se puede desarrollar, desplegar y escalar de forma independiente.
-
Explicación Detallada: Es la evolución natural de DDD y la Arquitectura Limpia en sistemas grandes. En una OTA, en lugar de tener un gigantesco monolito, tendrías un
Servicio de Búsqueda
, unServicio de Reservas
, unServicio de Pagos
y unServicio de Notificaciones
. Si el equipo de búsqueda quiere desplegar una nueva versión con un algoritmo mejorado, puede hacerlo sin afectar al resto de la aplicación. El principal desafío es gestionar la comunicación y la consistencia de los datos entre servicios.
Parte 6: Cultura y Prácticas DevOps ⚙️
DevOps no es un rol, es una cultura de colaboración entre los equipos de desarrollo (Dev) y operaciones (Ops) para automatizar y optimizar el ciclo de vida del software.
1. Integración Continua (CI) y Entrega/Despliegue Continuo (CD)
-
Definición Ampliada:
- CI (Continuous Integration): Es la práctica de fusionar automáticamente los cambios de código de todos los desarrolladores en un repositorio central varias veces al día. Cada fusión dispara una compilación y una ejecución de pruebas automatizadas. Su objetivo es detectar problemas de integración de forma temprana.
- CD (Continuous Delivery/Deployment): La Entrega Continua asegura que cada cambio que pasa las pruebas de CI se pueda desplegar a producción con un solo clic. El Despliegue Continuo va un paso más allá y despliega automáticamente a producción cada cambio que pasa las pruebas.
-
Ejemplo Práctico: "Usamos GitHub Actions. Cuando un desarrollador abre un Pull Request, un workflow de CI se dispara automáticamente. Este workflow instala las dependencias de Python, ejecuta un linter como Black, y corre las pruebas unitarias con
pytest
. Si todo pasa, se muestra una marca de verificación verde. Cuando el PR se fusiona a la rama principal, un segundo workflow de CD se activa, construye la imagen de Docker de nuestro microservicio y la sube a nuestro registro de contenedores en AWS (ECR), lista para ser desplegada."
2. Infraestructura como Código (IaC)
-
Definición Ampliada: Es la práctica de gestionar y aprovisionar la infraestructura (servidores, bases de datos, balanceadores de carga, redes) a través de archivos de código o definición, en lugar de hacerlo manualmente a través de una consola web. Permite tratar la infraestructura de la misma manera que el código de la aplicación: se puede versionar, probar y replicar.
-
Explicación Detallada: Esto hace que la infraestructura sea reproducible, versionable y auditable. Si necesitas replicar tu entorno de producción para hacer pruebas, simplemente ejecutas el mismo código IaC. Las herramientas más populares son Terraform (agnóstico a la nube) y AWS CloudFormation (nativo de AWS).
Parte 7: Despliegue en la Nube con AWS ☁️
Saber cómo desplegar tu aplicación es tan importante como saber construirla. AWS es el proveedor de nube dominante y conocer sus servicios clave es un gran plus.
1. Despliegue del Backend de Microservicios en Python
Una arquitectura común y moderna en AWS para microservicios sería:
- Contenerización: Cada microservicio de Python (hecho con FastAPI, por ejemplo) se empaqueta en una imagen de Docker.
- Registro de Contenedores: Las imágenes de Docker se almacenan en Amazon ECR (Elastic Container Registry).
- Orquestación/Ejecución: Tienes dos opciones principales:
- Amazon ECS (Elastic Container Service): Es el orquestador de contenedores nativo de AWS. Es más simple de gestionar que Kubernetes. Despliegas tus contenedores desde ECR como "servicios" en un clúster de ECS. Ideal para servicios que necesitan estar siempre corriendo.
- AWS Lambda (Serverless): Si tus microservicios son funciones que responden a eventos, puedes empaquetarlos como funciones Lambda. Es ideal para tareas cortas y te olvidas por completo de gestionar servidores.
- Punto de Entrada (API Gateway): Usas Amazon API Gateway para crear y gestionar los endpoints HTTP. El API Gateway recibe las peticiones de internet y las enruta al servicio de ECS o a la función Lambda correspondiente.
- Base de Datos: Cada microservicio debería tener su propia base de datos. Puedes usar Amazon RDS para bases de datos relacionales (como PostgreSQL) o Amazon DynamoDB para NoSQL.
2. Despliegue del Frontend con Angular
El despliegue de una Single Page Application (SPA) como Angular es más sencillo:
- Compilación (Build): En tu pipeline de CI/CD, ejecutas el comando
ng build
. Esto genera una carpeta/dist
con todos los archivos estáticos (HTML, CSS, JavaScript) optimizados para producción. - Alojamiento Estático (S3): El contenido de la carpeta
/dist
se sube a un bucket de Amazon S3. Este bucket se configura para "alojamiento de sitios web estáticos". - Red de Entrega de Contenidos (CDN): Creas una distribución de Amazon CloudFront que apunte a tu bucket de S3. CloudFront es un CDN que cachea tu aplicación en servidores de todo el mundo (edge locations). Los usuarios experimentarán tiempos de carga muy rápidos y además obtienes HTTPS fácilmente.
Parte 8: Proyectos Prácticos (Industria de Pagos y Turismo)
Proyecto 1: Plataforma de Pagos Simplificada (Estilo Stripe Básico)
- Backend (Python/FastAPI):
- Modelos (SQLAlchemy/Pydantic):
Customer
,PaymentMethod
(almacenando solo el token, no el PAN),Charge
(cargo). - API Endpoints:
POST /v1/customers
: Crea un nuevo cliente.POST /v1/payment_methods
: Adjunta un método de pago a un cliente (recibe un token del frontend).POST /v1/charges
: Crea un cargo. Recibe el ID del cliente, el ID del método de pago y el monto. Simula la lógica de cobro.
- Modelos (SQLAlchemy/Pydantic):
- Frontend (Angular):
- Componentes:
CustomerCreationComponent
,PaymentMethodFormComponent
(que se integre con una librería como Stripe.js para tokenizar la tarjeta),CheckoutComponent
. - Servicios:
CustomerService
,PaymentService
para interactuar con tu API.
- Componentes:
Proyecto 2: Agencia de Viajes Online (OTA) - Búsqueda y Reserva de Hoteles
- Backend (Python/FastAPI):
- Modelos:
Hotel
,Room
,Booking
,User
. - API Endpoints:
GET /hotels/search
: Busca hoteles por destino, fechas y huéspedes.GET /hotels/{hotel_id}/availability
: Verifica la disponibilidad de habitaciones para un hotel en ciertas fechas.POST /bookings
: Crea una nueva reserva para un usuario autenticado (requiere JWT).GET /users/me/bookings
: Devuelve las reservas del usuario actual (protegido).
- Modelos:
- Frontend (Angular):
- Componentes:
SearchFormComponent
,SearchResultsComponent
,HotelDetailsComponent
,BookingFormComponent
,MyBookingsComponent
(protegido con un Route Guard). - Servicios:
AuthService
(para login y manejo de JWT),HotelService
,BookingService
.
- Componentes: