ESC cerrar
Software Engineering Curriculum

Arquitectura & Ingeniería

Progresión estructurada desde fundamentos hasta arquitecturas enterprise

Nivel 1
Fundamentos
Nivel 2
Diseño
Nivel 3
Arquitectura
Nivel 4
Data & DevOps
18+ Conceptos
4 Niveles
Industry Basado en

Data Pipeline End-to-End

Arquitectura moderna de flujo de datos para procesamiento en tiempo real y por lotes

Pipelines
Data Lifecycle
Patterns & Governance
Software Fundamentals
Testing and Quality
Real-time Streaming Pipeline
Latencia: <1s 100K+ events/s 24/7 Continuous
01

Ingest

Event Streaming

02

Process

Procesamiento de Streams

03

Store

Almacenamiento en Tiempo Real

04

Serve

Real-time Dashboards

EVENT BROKER Apache Kafka
Streaming
12 Brokers 50 Topics
SCHEMA REGISTRY Confluent
Validating
Avro/Proto Versioned
MONITORING Prometheus + Grafana
Live Metrics
Alerting Dashboards
Batch Processing Pipeline
Petabytes Daily/Hourly ACID Compliant
01

Extract

Fuente de Datoss

02

Transform

ETL / ELT

04

Analyze

BI & Analytics

ORCHESTRATION Apache Airflow
DAG Active
Cron: 0 6 * * * Retries: 3
DATA QUALITY Great Expectations
Tests Passing
47 Expectations 99.8% Pass
DATA CATALOG DataHub / Atlan
Cataloging
156 Tables Lineage
etl_dag.py
Airflow 2.x Python 3.11
# etl_dag.py - Pipeline de datos completo
from airflow import DAG
from airflow.operators.python import PythonOperator
from airflow.providers.postgres.operators.postgres import PostgresOperator
from datetime import datetime, timedelta

# Configuración del DAG
default_args = {
    'owner': 'data-team',
    'depends_on_past': False,
    'start_date': datetime(2025, 1, 1),
    'retries': 3,
    'retry_delay': timedelta(minutes=5),
}

with DAG(
    'etl_pipeline_prod',
    default_args=default_args,
    schedule_interval='0 6 * * *',  # Diario a las 6 AM
    catchup=False,
    tags=['etl', 'production']
) as dag:

    # Task 1: Extraer datos desde API
    extract = PythonOperator(
        task_id='extract_from_api',
        python_callable=extract_data
    )

    # Task 2: Transformar con Spark
    transform = PythonOperator(
        task_id='transform_spark',
        python_callable=spark_transform
    )

    # Task 3: Cargar a Data Warehouse
    load = PostgresOperator(
        task_id='load_to_warehouse',
        postgres_conn_id='warehouse_conn',
        sql='sql/load_facts.sql'
    )

    # Definir dependencias
    extract >> transform >> load

Modern Data Platform Architecture

Enterprise-Grade End-to-End Data Flow
LIVE
2.4M events/min 99.9% uptime
Apache Airflow Orchestration & Scheduling
156 DAGs
12K Tasks/day
99.8% Success
All Systems Operational
DAMA-DMBOK Collibra Framework

Enterprise Data Governance

Políticas, procesos y roles para asegurar datos precisos, disponibles y confiables
COMPLIANCE STATUS 100%
GDPR EU Privacy
CCPA California
SOC 2 Type II
HIPAA Healthcare
ISO 27001 Security

Los 5 Pilares de Data Governance

Framework de Collibra para gobierno de datos efectivo
Collibra
01
Definiciones Consistentes

Vocabulario empresarial común que asegura que todos hablen el mismo idioma de datos

Business Glossary Data Dictionary Semantic Layer
1,240 Terms Defined
02
Control de Acceso

Políticas que definen quién puede ver, usar y modificar cada activo de datos

RBAC/ABAC Dynamic Masking Encryption
156 Policies Active
03
Gestión de Relaciones

Mapeo de dependencias y linaje de datos desde origen hasta consumo

Data Lineage Impact Analysis Dependencies
2,890 Lineage Paths
04
Cumplimiento Regulatorio

Adherencia a regulaciones de privacidad y estándares de la industria

GDPR/CCPA Audit Trails Retention
100% Compliant
05
Automatización de Procesos

Workflows automatizados para solicitudes, aprobaciones y remediación

Workflows Alerts Auto-Remediation
n8n
45 Workflows Active

Data Lifecycle Management

Gestión del ciclo de vida completo de los datos
Create Ingesta, Captura
Store Raw, Staging
Use Analytics, ML
Archive Cold Storage
Destroy Retention Policy

Roles & Responsabilidades

Estructura organizacional para Data Governance
Data Owner
Ejecutivo
  • Define políticas de acceso
  • Aprueba usos de datos
  • Responsable del dominio
ACCOUNTABLE
Data Steward
Táctico
  • Mantiene calidad de datos
  • Gestiona metadatos
  • Resuelve issues de datos
RESPONSIBLE
Data Custodian
Técnico
  • Implementa controles
  • Gestiona infraestructura
  • Asegura disponibilidad
SUPPORT
Data Consumer
Operacional
  • Usa datos según políticas
  • Reporta issues de calidad
  • Solicita acceso formal
INFORMED

Data Classification Framework

Clasificación basada en sensibilidad y criticidad
PUBLIC 42 assets
Información disponible públicamente sin restricciones
Open Access
INTERNAL 89 assets
Solo para uso interno de la organización
Internal Only Auth Required
CONFIDENTIAL 18 assets
Acceso restringido basado en necesidad de negocio
Restricted Approval Audit Log
PII / SENSITIVE 7 assets
Datos personales identificables bajo regulación
Masked Encrypted Retention Right to Delete

Data Governance Maturity

Evaluación del nivel de madurez del programa
1
Initial Ad-hoc
2
Managed Reactive
3
Defined Proactive
4
Measured Quantified
Current
5
Optimized Continuous
Business Intelligence Reporting Self-Service

Data Visualization & Reporting

Transforma datos en insights accionables para toda la organización
4 Plataformas
100+ Visualizaciones
24/7 Dashboards

Plataformas de Visualización

Las herramientas líderes de BI y Analytics
Power BI
Microsoft
Enterprise

Plataforma de analytics de Microsoft con integración nativa a Azure y Microsoft 365

DAX & Power Query
Copilot AI
100+ Conectores
Mobile App
Desktop Service Mobile Embedded
Tableau
Salesforce
Market Leader

Líder en visualización analítica con VizQL y exploración de datos intuitiva

Tableau Pulse
Ask Data (NLP)
VizQL
Tableau Cloud
Desktop Server Cloud Prep
Looker
Google Cloud
Semantic Layer

BI moderno con LookML para definir métricas consistentes en toda la organización

LookML
Explores
Embedded SDK
Alerts
Looker Studio API Embed
Streamlit
Snowflake
Python-First

Framework open-source para crear data apps interactivas con Python puro

Pure Python
Widgets
Hot Reload
Community Cloud
Core Components Cloud Snowflake

Tipos de Visualización

Elige la visualización correcta según el tipo de análisis
Comparación
Bar Chart Compara valores entre categorías discretas
Ventas por región, Top productos
Básico
Column Chart Comparación vertical de valores
Revenue mensual, Usuarios activos
Básico
Stacked Bar Composición dentro de categorías
Mix de productos, Market share
Intermedio
Waterfall Muestra incrementos/decrementos acumulativos
P&L breakdown, Cash flow
Avanzado
Tendencias en el Tiempo
Line Chart Evolución continua de métricas
Tendencia ventas, Stock prices
Básico
Area Chart Magnitud acumulada en el tiempo
Revenue acumulado, Traffic
Intermedio
Sparklines Mini-gráficos inline en tablas
KPIs en cards, Tablas de stock
Avanzado
Candlestick Open-High-Low-Close para trading
Stock trading, Crypto analysis
Avanzado
Distribución y Relaciones
Scatter Plot Correlación entre dos variables
Precio vs Demanda, Age vs Income
Intermedio
Bubble Chart 3 variables: X, Y, y tamaño
Market analysis, Risk matrix
Avanzado
Heat Map Densidad de datos en matriz
Correlation matrix, Usage patterns
Avanzado
Treemap Jerarquías con proporciones
Disk usage, Budget allocation
Avanzado
Composición
Pie Chart Partes de un todo (máx 5-7 slices)
Market share, Budget split
Básico
Donut Chart Pie con KPI central
Completion rate, Goal progress
Intermedio
Sunburst Composición jerárquica multi-nivel
Org breakdown, File system
Avanzado
Sankey Diagram Flujos y transferencias
User journey, Energy flow
Avanzado
KPIs y Métricas
KPI Card Número grande con contexto
Revenue actual, Total users
Básico
Gauge Progreso hacia un objetivo
SLA %, Quota attainment
Intermedio
Bullet Chart Actual vs Target compacto
Sales vs Goal, Performance
Intermedio
Progress Bar Avance porcentual simple
Project status, Loading
Básico
Geoespacial y Datos Detallados
Choropleth Map Regiones coloreadas por valor
Sales by country, Demographics
Intermedio
Bubble Map Puntos geográficos con magnitud
Store locations, Event density
Avanzado
Data Table Detalle granular con ordenamiento
Transaction list, Inventory
Básico
Matrix/Pivot Tabla multidimensional dinámica
Sales by region/product, Reports
Intermedio

Estructura de Dashboards & Workbooks

Organización jerárquica de contenido analítico
Workbook: Sales Analytics 2025
4 Dashboards Last updated: 2h ago Shared with 12 users
Executive Overview Real-time sales metrics and KPIs
Last 30 days All Regions Add Filter
Total Revenue $4.2M 12.5%
Orders 8,432 8.2%
Avg Order Value $498 2.1%
Conversion Rate 3.8% 0.5%
Revenue Trend
Sales by Region
Top Products
Regional Analysis Geographic performance breakdown
YTD 2025 Americas
LATAM Revenue $1.8M 18.3%
NA Revenue $2.1M 9.7%
Active Countries 28 +4
Top Region Chile #1
Sales by Country
Choropleth Map
Region Comparison
Chile
Brasil
México
Product Performance SKU analysis and inventory
Q4 2024 All Categories
Active SKUs 2,847 +156
Top Category Electronics 34%
Avg Rating 4.6★ 0.2
Low Stock 23 Alert
Top 5 Products
MacBook Pro
$890K
iPhone 15
$720K
AirPods Pro
$540K
iPad Air
$480K
Apple Watch
$380K
Category Mix
Trends & Forecasting Historical analysis and predictions
2023-2025 ML Forecast
YoY Growth +24.5% Strong
Q1 2025 Forecast $5.8M +38%
Peak Season Nov-Dec +65%
Model Accuracy 94.2% High
Revenue Trend with Forecast
Actual Forecast
Seasonality
Jan Feb Mar
Apr May Jun
Jul Aug Sep
Oct Nov Dec

Flujo de Reporting

Del dato crudo al insight accionable
Fuente de Datos DW / Lake
Semantic Layer Métricas
Visualization Dashboards
Consumption Self-Service
Action Decisiones
ETL ELT Streaming

Data Ingestion Pipeline

Extracción y movimiento de datos desde múltiples fuentes hacia el Data Warehouse
20+ APIs
50TB Daily Volume
99.9% Uptime

Fuente de Datoss

Orígenes de datos para extracción

ETL vs ELT

Patrones de integración de datos
ETL
Extract → Transform → Load
Source
Transform
DW
  • Transformación antes de cargar
  • Menor storage en destino
  • Datos ya limpios al llegar
VS
ELT
Extract → Load → Transform
Source
DW
Transform
  • Usa poder del Cloud DW
  • Raw data disponible
  • Más flexible y moderno

Medallion Architecture

Capas de datos: Bronze → Silver → Gold
Bronze
Raw Data
  • Datos crudos sin procesar
  • Schema-on-read
  • Histórico completo
Parquet / Delta
Silver
Cleaned Data
  • Validación y limpieza
  • Tipos de datos correctos
  • Deduplicación
Delta Lake
Gold
Business-Ready
  • Agregaciones y métricas
  • Modelos dimensionales
  • Listo para BI
Curated Tables
DAGs Workflows Scheduling

Data Orchestration

Coordinación y automatización de workflows de datos complejos
156 DAGs
12K Tasks/Day
99.8% Success

Plataformas de Orquestación

Los 3 líderes del ecosistema 2024
#1
Apache Airflow
Apache Foundation
320M Downloads

El estándar de la industria para orquestación de workflows de datos, nacido en Airbnb

DAGs en Python
1000+ Operators
Web UI
Comunidad Masiva
ETL Pipelines ML Workflows Data Ops
#2
Dagster
Elementl
Asset-Centric

Orquestador moderno centrado en assets, no en tareas. Diseñado para el ciclo de vida completo

Software-Defined Assets
Type System
Testing Built-in
Dagit UI
Data Assets dbt Integration ML Pipelines
#3
Prefect
Prefect Technologies
Pythonic

Orquestación simple y flexible con decoradores Python. Filosofía de "ingeniería negativa"

Pure Python
Decorators
Prefect Cloud
Async Native
Dynamic Workflows API Pipelines Rapid Dev

DAG Example

Directed Acyclic Graph - Flujo de tareas
Start
Extract API
Extract Files
Extract Stream
Transform
Load DW
Notify
Quality Check

Scheduling Patterns

Patrones de ejecución de workflows
Cron-based
0 */2 * * * Cada 2 horas
Event-driven
on: file_arrival Trigger por evento
Data-aware
@asset_updated Cuando el asset cambia
Manual
trigger_dag() Ejecución manual

Design Patterns

Arquitecturas probadas para pipelines de datos escalables

Medallion CDC Data Mesh Data Fabric CQRS SCD

Medallion Architecture

Patrón de organización de datos multicapa para Data Lakehouses

Bronze (Raw)

Datos crudos sin transformación

  • Copia exacta de la fuente
  • Sin validación
  • Solo agregar
  • Historial completo
Parquet Timestamp
Silver (Validated)

Datos limpios y conformados

  • Deduplicación
  • Validación de esquema
  • Verificaciones de calidad de datos
  • Joins básicos
Delta Lake ACID
Gold (Curated)

Datos listos para análisis

  • Agregaciones de negocio
  • KPIs y métricas
  • Almacenes de características
  • Modelos semánticos
BI Ready ML Ready

Change Data Capture (CDC)

Rastrea y captura cambios de base de datos en tiempo real para sistemas downstream

Source DB PostgreSQL, MySQL, MongoDB
Log de Transacciones
Debezium Debezium Conector CDC
Kafka Topics Flujo de Eventos
Data Warehouse
Search Index
Microservices

Data Mesh

Propiedad descentralizada de datos con arquitectura orientada a dominios

Domain Ownership

Los equipos son dueños de sus datos de principio a fin

Data as a Product

Tratar datos con pensamiento de producto

Self-serve Platform

Infraestructura como plataforma

Federated Governance

Descentralizado con estándares

Data Fabric

Integración de datos unificada usando metadatos activos e IA/ML

Capa de Conocimiento
Knowledge Graph Active Metadata Semantic Layer
Capa de Integración
Data Virtualization API Management ETL/ELT
Capa de Gobernanza
Data Catalog Lineage Quality
30% Diseño de integración más rápido
70% Menos mantenimiento

CQRS + Event Sourcing

Modelos separados de lectura y escritura para sistemas escalables orientados a eventos

Lado de Comandos (Escritura)
Command
Lógica de Dominio
Event Store
Events
Lado de Consultas (Lectura)
Modelo de Lectura
Projections
Event Store

Slowly Changing Dimensions (SCD)

Estrategias para rastrear cambios históricos en tablas de dimensión

Type 0
Retener Original

Nunca actualizar valor original

-- No UPDATE allowed
Type 1
Sobrescribir

Reemplazar con nuevo valor

UPDATE dim SET name = 'New'
Type 2
Agregar Nueva Fila

Mantener historial completo

INSERT + valid_from/to
Type 3
Agregar Nueva Columna

Almacenar anterior + actual

current_val, previous_val

Data Vault 2.0

Metodología ágil y escalable de modelado de data warehouse

Hubs

Claves de negocio (identificadores únicos)

Satellites

Atributos descriptivos + historial

Processing Architectures

Patrones escalables para procesamiento moderno de datos

Lambda Kappa Delta ETL vs ELT Streaming

Lambda Architecture

Procesamiento paralelo batch y streaming para resultados precisos y de baja latencia

Fuente de Datos
Batch Layer

Procesa todos los datos históricos

Spark Hadoop
Vistas Batch
Speed Layer

Procesa datos recientes en tiempo real

Kafka Flink
Vistas en Tiempo Real
Serving Layer Combina batch + tiempo real
Ventajas
  • Resultados precisos (batch) + baja latencia (speed)
  • Tolerante a fallos
  • Fácil reprocesamiento
Desventajas
  • Complejidad de mantener 2 pipelines
  • Código duplicado
  • Mayor costo operacional

Kappa Architecture

Lambda simplificado usando solo streaming para todo el procesamiento

Fuente de Datos
Procesamiento de Streams Pipeline único
Serving Layer
Código único
Reprocesa via replay
Tiempo real primero

Delta Architecture

Batch y streaming unificado en Delta Lake con transacciones ACID

ACID Transactions

Garantías transaccionales en data lakes

Time Travel

Consultar versiones anteriores de datos

Unified Batch & Streaming

Mismo formato para ambos paradigmas

Schema Enforcement

Validación automática de esquema

ETL vs ELT

Dos enfoques fundamentales para integración y transformación de datos

ETL
Extract → Transform → Load
Source
Transform
Target DW
  • La transformación ocurre en servidor staging
  • Enfoque tradicional, más lento
  • SSIS, Informatica, Talend
VS
ELT
Extract → Load → Transform Modern
Source
Data Lake/DW
Transform
  • Transformación en destino (escalable)
  • Más rápido, aprovecha el cómputo del DW
  • dbt, Fivetran, Airbyte, Stitch
Ejemplo ELT con dbt SQL + Jinja
-- models/staging/stg_orders.sql
{{ config(materialized='incremental') }}

SELECT
    order_id,
    customer_id,
    order_date,
    total_amount,
    {{ dbt_utils.generate_surrogate_key(['order_id']) }} as order_sk
FROM {{ source('raw', 'orders') }}
{% if is_incremental() %}
WHERE order_date > (SELECT MAX(order_date) FROM {{ this }})
{% endif %}

Streaming Patterns

Estrategias de procesamiento en tiempo real para flujos de datos continuos

Windowing

Ventanas Tumbling, Sliding, Session para agregaciones basadas en tiempo

Watermarks

Manejo de datos tardíos con procesamiento de tiempo de evento

Exactly-Once

Garantiza que cada evento se procese exactamente una vez

Backpressure

Control de flujo cuando los consumidores no pueden seguir el ritmo

Storage Platforms

Comparación de arquitecturas de datos modernas y modelado dimensional

Data Lake Data Warehouse Lakehouse Star Schema Snowflake Schema

Data Lake

Repositorio centralizado para almacenar datos crudos a escala

Cualquier formato (JSON, CSV, Parquet, Avro)
Bajo costo de almacenamiento
Altamente escalable
Sin garantías ACID

Data Warehouse

Almacenamiento estructurado optimizado para analytics y BI

Schema-on-write (estructura predefinida)
Cumplimiento ACID completo
Consultas optimizadas
Mayor costo por TB

Data Lakehouse

Recomendado

Combina la flexibilidad del Data Lake con las capacidades del Data Warehouse

Schema-on-read + Schema-on-write
ACID on Parquet files
Viaje en el tiempo y versionado
Unified Batch + Streaming

Open Table Formats

Formatos abiertos que habilitan funcionalidad Lakehouse en Data Lakes

Delta Lake

Databricks

  • Transacciones ACID
  • Evolución de esquema
  • Viaje en el tiempo
  • Z-ordering
delta.io
Apache Iceberg

Netflix → Apache

  • Particionamiento oculto
  • Integración con catálogo
  • Viaje en el tiempo
  • Neutral de proveedor
iceberg.apache.org
Apache Hudi

Uber → Apache

  • Upserts optimizados
  • Consultas incrementales
  • Cambios a nivel de registro
  • Soporte CDC
hudi.apache.org

Dimensional Modeling

Patrones clásicos de diseño de data warehouse por Ralph Kimball

Star Schema
Más Popular
Fact Table Ventas, Órdenes, Transacciones
dim_date
dim_customer
dim_product
dim_store
  • Consultas simples (menos JOINs)
  • Mejor rendimiento de consultas
  • Dimensiones desnormalizadas
  • Redundancia de datos
VS
Snowflake Schema
Normalizado
Fact Table Ventas, Órdenes, Transacciones
dim_date
dim_time
dim_product
dim_category
dim_brand
  • Menos redundancia de datos
  • Normalizado dimensions
  • Mantenimiento más fácil
  • Consultas más complejas
Consejo: Star Schema es preferido para la mayoría de cargas BI/analytics debido a consultas más simples y mejor rendimiento con bases de datos columnares modernas.
Programación Orientada a Objetos (POO)
PRY2202 DuocUC Industry Practices 4 Pilares
Asignatura PRY2202 - Desarrollo Orientado a Objetos I (Bimestre 02). Paradigma fundamental en Java, Python, C# y la mayoría de lenguajes modernos.

Encapsulamiento

Ocultar datos internos (private) y controlar acceso mediante métodos públicos (getters/setters).

public class Empleado {
    private String rut;  // Oculto
    private double salario;

    public String getRut() {
        return this.rut;
    }

    public void setSalario(double s) {
        if (s >= 0) this.salario = s;
    }
}
privateprotectedgetterssetters

Herencia

Crear clases hijas que heredan atributos y métodos de una clase padre. Reutilización de código.

public class Persona {
    protected String nombre;
}

public class Estudiante extends Persona {
    private String matricula;

    public Estudiante(String n, String m) {
        super();  // Llama constructor padre
        this.nombre = n;
        this.matricula = m;
    }
}
extendssuper()protected

Polimorfismo

"Muchas formas": Un mismo método con diferentes comportamientos según la clase que lo implemente.

public abstract class Vehiculo {
    public abstract void acelerar();
}

public class Auto extends Vehiculo {
    @Override
    public void acelerar() {
        System.out.println("Pedal");
    }
}

// Una variable padre puede referenciar hijos
Vehiculo v = new Auto();
v.acelerar(); // Ejecuta versión de Auto
@Overrideabstractdynamic binding

Abstracción

Definir contratos (interfaces) que especifican QUÉ hacer, no CÓMO. Desacoplar implementación.

public interface Registrable {
    String getId();
    boolean validar();
}

public class Producto implements Registrable {
    private String codigo;

    @Override
    public String getId() {
        return this.codigo;
    }

    @Override
    public boolean validar() {
        return codigo != null;
    }
}
interfaceimplementscontract
Competencias PRY2202:
Diseñar clases con encapsulamiento Implementar jerarquías de herencia Aplicar polimorfismo en colecciones Definir e implementar interfaces
Lenguajes OOP:
Functional Programming (FP)
Lambda Calculus LISP 1958 Pipelines

Pure Functions

Mismo input produce mismo output. Sin efectos secundarios.

f(x) = x * 2

Immutability

Los datos no se modifican despues de crearse.

const newList = [...list, item]

Higher-Order Functions

Funciones que reciben o retornan funciones.

list.map(x => x * 2)

Composition

Combinar funciones para crear nuevas funciones.

compose(f, g)(x)
Aplicaciones en Data Engineering:
Spark RDDs Stream Processing ETL Pipelines
Lenguajes FP:
Principios SOLID
Robert C. Martin Clean Architecture 5 Principios
Los 5 principios de diseño OO que hacen el código más mantenible, extensible y testeable. Aplicables en Java, C#, Python y todos los lenguajes OOP.
S

Single Responsibility Principle (SRP)

Una clase debe tener una sola razón para cambiar. Separar responsabilidades en clases especializadas.

MAL class Empleado { guardarBD(); validar(); enviarEmail(); }
BIEN class Empleado + EmpleadoValidator + EmpleadoRepository
O

Open/Closed Principle (OCP)

Abierto para extensión, cerrado para modificación. Usar herencia o interfaces para agregar comportamiento.

MAL if (tipo == "tarjeta") {...} else if (tipo == "paypal") {...}
BIEN interface MetodoPago { procesar(); } → TarjetaPago, PayPalPago
L

Liskov Substitution Principle (LSP)

Subclases deben ser sustituibles por su clase base sin romper el programa. Diseño de herencia correcto.

MAL class Cuadrado extends Rectangulo // setWidth rompe setHeight
BIEN interface Forma { getArea(); } → Cuadrado, Rectangulo
I

Interface Segregation Principle (ISP)

Interfaces pequeñas y específicas mejor que una interfaz grande. No obligar a implementar métodos no usados.

MAL interface Trabajador { trabajar(); comer(); dormir(); }
BIEN interface Trabajable + Alimentable + Descansable
D

Dependency Inversion Principle (DIP)

Depender de abstracciones, no de concretos. Inyectar dependencias mediante interfaces.

MAL class Servicio { private MySQLRepo repo = new MySQLRepo(); }
BIEN class Servicio { Servicio(IRepository repo) {...} }
Beneficios de aplicar SOLID:
Código mantenible Fácil de extender Testeable (unit tests) Trabajo en equipo
Clean Code Practices
Uncle Bob (2008) Best Seller Readable Code

Meaningful Names

Nombres que revelan intencion. Evitar abreviaciones.

int d;
int elapsedDays;

Small Functions

Funciones pequenas que hacen una sola cosa.

Menos de 20 lineas

Comments

El codigo debe ser auto-documentado.

i++; // increment i

Code Smells

Long Method, Duplicate Code, God Class.

Extract Method
Testing Avanzado
Mike Cohn 6 Técnicas Code Examples

El Testing de Software es fundamental para garantizar calidad. La Testing Pyramid de Mike Cohn establece la proporción óptima de tests. Técnicas avanzadas como TDD, BDD, Contract Testing y Mutation Testing mejoran la efectividad de las pruebas.

Más lento, más costoso Más rápido, más económico
Haz clic en un nivel de la pirámide o metodología para ver el código de ejemplo

TDD - Test Driven Development

Red → Green → Refactor
1
RED
Escribir test que falla
2
GREEN
Código mínimo que pasa
3
REFACTOR
Mejorar sin romper
JUnit 5
// TDD - Test Driven Development con JUnit 5
// Ciclo: RED → GREEN → REFACTOR

// STEP 1: RED - Escribir test que falla
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {

    @Test
    @DisplayName("Suma de dos números positivos")
    void testSumaPositivos() {
        Calculator calc = new Calculator();
        assertEquals(5, calc.sumar(2, 3));
    }

    @Test
    @DisplayName("Suma con número negativo")
    void testSumaConNegativo() {
        Calculator calc = new Calculator();
        assertEquals(-1, calc.sumar(2, -3));
    }

    @Test
    @DisplayName("División por cero lanza excepción")
    void testDivisionPorCero() {
        Calculator calc = new Calculator();
        assertThrows(ArithmeticException.class,
            () -> calc.dividir(10, 0));
    }
}

// STEP 2: GREEN - Implementar código mínimo
public class Calculator {
    public int sumar(int a, int b) {
        return a + b;
    }

    public int dividir(int a, int b) {
        if (b == 0) {
            throw new ArithmeticException("División por cero");
        }
        return a / b;
    }
}

// STEP 3: REFACTOR - Mejorar sin romper tests
// Los tests pasan → código validado ✓

BDD - Behavior Driven Development

Given → When → Then
Given Contexto inicial
When Acción del usuario
Then Resultado esperado
Cucumber + Gherkin
// BDD - Behavior Driven Development
// Lenguaje: Gherkin (Given-When-Then)

// login.feature
Feature: Inicio de sesión de usuario
  Como usuario registrado
  Quiero iniciar sesión en el sistema
  Para acceder a mi cuenta

  Scenario: Login exitoso con credenciales válidas
    Given un usuario registrado con email "juan@email.com"
    And su contraseña es "Password123"
    When el usuario ingresa sus credenciales
    And hace clic en "Iniciar Sesión"
    Then debe ver el mensaje "Bienvenido, Juan"
    And debe ser redirigido al dashboard

  Scenario: Login fallido con contraseña incorrecta
    Given un usuario registrado con email "juan@email.com"
    When el usuario ingresa email "juan@email.com"
    And ingresa contraseña "wrongpassword"
    And hace clic en "Iniciar Sesión"
    Then debe ver el mensaje de error "Credenciales inválidas"
    And debe permanecer en la página de login

// LoginSteps.java - Implementación de pasos
import io.cucumber.java.en.*;

public class LoginSteps {
    private LoginPage loginPage;
    private String mensaje;

    @Given("un usuario registrado con email {string}")
    public void usuarioRegistrado(String email) {
        // Setup: crear usuario en BD de prueba
    }

    @When("el usuario ingresa sus credenciales")
    public void ingresaCredenciales() {
        loginPage.enterCredentials(email, password);
    }

    @Then("debe ver el mensaje {string}")
    public void verificarMensaje(String expected) {
        assertEquals(expected, loginPage.getMessage());
    }
}

Contract Testing

Consumer-Driven
Consumer Frontend/Mobile
Contract
Provider Backend API
Pact.js
// Contract Testing con Pact
// Verifica contratos entre servicios (Consumer-Driven)

// Consumer Side (Frontend/Mobile)
const { Pact } = require('@pact-foundation/pact');

describe('User API Contract', () => {
  const provider = new Pact({
    consumer: 'FrontendApp',
    provider: 'UserService',
    port: 1234
  });

  beforeAll(() => provider.setup());
  afterAll(() => provider.finalize());

  describe('GET /api/users/:id', () => {
    it('returns user data for valid ID', async () => {
      // Define expected interaction
      await provider.addInteraction({
        state: 'user with ID 1 exists',
        uponReceiving: 'a request for user 1',
        withRequest: {
          method: 'GET',
          path: '/api/users/1',
          headers: { 'Accept': 'application/json' }
        },
        willRespondWith: {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
          body: {
            id: 1,
            name: Matchers.string('John Doe'),
            email: Matchers.email()
          }
        }
      });

      // Execute consumer code
      const user = await userClient.getUser(1);
      expect(user.name).toBeDefined();
    });
  });
});

// Provider Side (Backend) - Verifica contra el contrato
// El Pact Broker almacena los contratos generados

Mutation Testing

Test Quality
Código Original if (x > 10)
Mutación
Mutante if (x >= 10)
Test detecta = Mutante Killed Test no detecta = Mutante Survived
PIT (Java)
// Mutation Testing con PIT (Java)
// Evalúa la calidad de los tests introduciendo mutaciones

// pom.xml - Configuración de PIT

    org.pitest
    pitest-maven
    1.15.0
    
        
            com.myapp.services.*
        
        
            com.myapp.services.*Test
        
        
            CONDITIONALS_BOUNDARY
            INCREMENTS
            MATH
            NEGATE_CONDITIONALS
            RETURN_VALS
        
    


// Código Original
public class PriceCalculator {
    public double calculateDiscount(double price, int quantity) {
        if (quantity > 10) {  // Mutación: > → >=
            return price * 0.9;  // Mutación: 0.9 → 0.8
        }
        return price;
    }
}

// Test Original - ¿Detecta las mutaciones?
@Test
void testDescuentoPorCantidad() {
    PriceCalculator calc = new PriceCalculator();
    // Este test NO detecta mutación ">" → ">="
    assertEquals(90.0, calc.calculateDiscount(100.0, 15));
}

// Test Mejorado - Detecta boundary mutation
@Test
void testBoundaryCondition() {
    PriceCalculator calc = new PriceCalculator();
    // Exactly 10 items → NO discount
    assertEquals(100.0, calc.calculateDiscount(100.0, 10));
    // 11 items → WITH discount
    assertEquals(90.0, calc.calculateDiscount(100.0, 11));
}

// Ejecutar: mvn org.pitest:pitest-maven:mutationCoverage
// Reporte: target/pit-reports/index.html

Integration Testing

~20% Pyramid
Controller
Service
Repository
Database (H2/TestContainers)
Spring Boot Test
// Integration Testing con Spring Boot
// Prueba la interacción entre componentes reales

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.test.context.TestPropertySource;
import org.junit.jupiter.api.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(locations = "classpath:application-test.properties")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class UserControllerIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private UserRepository userRepository;

    @BeforeEach
    void setup() {
        userRepository.deleteAll();
    }

    @Test
    @Order(1)
    @DisplayName("POST /api/users - Crear usuario")
    void createUser() throws Exception {
        String userJson = """
            {
                "name": "Juan Pérez",
                "email": "juan@email.com",
                "role": "USER"
            }
            """;

        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(userJson))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.id").exists())
            .andExpect(jsonPath("$.name").value("Juan Pérez"));

        // Verificar que se guardó en BD real
        assertTrue(userRepository.findByEmail("juan@email.com").isPresent());
    }

    @Test
    @Order(2)
    @DisplayName("GET /api/users/{id} - Obtener usuario existente")
    void getUserById() throws Exception {
        // Arrange: crear usuario directamente en BD
        User saved = userRepository.save(new User("Test", "test@email.com"));

        // Act & Assert
        mockMvc.perform(get("/api/users/" + saved.getId()))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("Test"));
    }
}

E2E Testing

~10% Pyramid
Usuario
Browser
Backend
Database
Playwright
// E2E Testing con Playwright
// Prueba flujos completos desde la perspectiva del usuario

import { test, expect } from '@playwright/test';

test.describe('Flujo de Compra E-Commerce', () => {

  test.beforeEach(async ({ page }) => {
    await page.goto('https://myshop.com');
  });

  test('Usuario completa compra exitosamente', async ({ page }) => {
    // 1. Buscar producto
    await page.fill('[data-testid="search-input"]', 'laptop');
    await page.click('[data-testid="search-button"]');
    await expect(page.locator('.product-card')).toHaveCount.greaterThan(0);

    // 2. Seleccionar producto
    await page.click('.product-card:first-child');
    await expect(page).toHaveURL(/\/products\/\d+/);

    // 3. Agregar al carrito
    await page.click('[data-testid="add-to-cart"]');
    await expect(page.locator('.cart-badge')).toHaveText('1');

    // 4. Ir al checkout
    await page.click('[data-testid="cart-icon"]');
    await page.click('[data-testid="checkout-button"]');

    // 5. Completar datos de envío
    await page.fill('#shipping-name', 'Juan Pérez');
    await page.fill('#shipping-address', 'Av. Principal 123');
    await page.fill('#shipping-city', 'Santiago');
    await page.click('[data-testid="continue-payment"]');

    // 6. Completar pago (sandbox)
    await page.fill('#card-number', '4242424242424242');
    await page.fill('#card-expiry', '12/25');
    await page.fill('#card-cvv', '123');
    await page.click('[data-testid="pay-button"]');

    // 7. Verificar confirmación
    await expect(page.locator('.order-confirmation')).toBeVisible();
    await expect(page.locator('.order-number')).toHaveText(/ORD-\d+/);
  });

  test('Validación de campos obligatorios', async ({ page }) => {
    await page.goto('/checkout');
    await page.click('[data-testid="continue-payment"]');
    await expect(page.locator('.error-message')).toContainText('Nombre requerido');
  });
});

Unit Testing

~70% Pyramid
Una unidad = Una función/método

Aislar dependencias con Mocks

Rápidos Aislados Repetibles Auto-verificables
JUnit 5
// TDD - Test Driven Development con JUnit 5
// Ciclo: RED → GREEN → REFACTOR

// STEP 1: RED - Escribir test que falla
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {

    @Test
    @DisplayName("Suma de dos números positivos")
    void testSumaPositivos() {
        Calculator calc = new Calculator();
        assertEquals(5, calc.sumar(2, 3));
    }

    @Test
    @DisplayName("Suma con número negativo")
    void testSumaConNegativo() {
        Calculator calc = new Calculator();
        assertEquals(-1, calc.sumar(2, -3));
    }

    @Test
    @DisplayName("División por cero lanza excepción")
    void testDivisionPorCero() {
        Calculator calc = new Calculator();
        assertThrows(ArithmeticException.class,
            () -> calc.dividir(10, 0));
    }
}

// STEP 2: GREEN - Implementar código mínimo
public class Calculator {
    public int sumar(int a, int b) {
        return a + b;
    }

    public int dividir(int a, int b) {
        if (b == 0) {
            throw new ArithmeticException("División por cero");
        }
        return a / b;
    }
}

// STEP 3: REFACTOR - Mejorar sin romper tests
// Los tests pasan → código validado ✓
Design Patterns (GoF)
Gang of Four 1994 23 Patterns

Los Design Patterns son soluciones reutilizables a problemas comunes en el diseño de software. El libro "Design Patterns: Elements of Reusable Object-Oriented Software" (1994) documentó 23 patrones clasificados en tres categorías.

Creational

Creación de objetos

Structural

Composición de clases

Behavioral

Comunicación entre objetos
Selecciona un patrón con HOT para ver el código de ejemplo

Singleton Pattern

Creational
Intención: Garantizar que una clase tenga una única instancia y proporcionar un punto de acceso global a ella.
DatabaseConnection
- instance: DatabaseConnection - connection: Connection
- DatabaseConnection() + getInstance(): DatabaseConnection + executeQuery(sql): void
Java DatabaseConnection.java
// Singleton Pattern - Thread-Safe (Double-Checked Locking)
public class DatabaseConnection {
    private static volatile DatabaseConnection instance;
    private Connection connection;

    private DatabaseConnection() {
        // Private constructor prevents direct instantiation
        this.connection = createConnection();
    }

    public static DatabaseConnection getInstance() {
        if (instance == null) {
            synchronized (DatabaseConnection.class) {
                if (instance == null) {
                    instance = new DatabaseConnection();
                }
            }
        }
        return instance;
    }

    public void executeQuery(String sql) {
        // Execute SQL using the single connection
    }
}

// Uso:
DatabaseConnection db = DatabaseConnection.getInstance();
db.executeQuery("SELECT * FROM users");
Casos de Uso
Conexiones BD Logger Cache Config Manager Thread Pool

Factory Method Pattern

Creational
Intención: Definir una interfaz para crear objetos, pero permitir que las subclases decidan qué clase instanciar.
«interface» Notification
+ send(message): void
EmailNotification
SMSNotification
PushNotification
Java NotificationFactory.java
// Factory Method Pattern
public interface Notification {
    void send(String message);
}

public class EmailNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("Email: " + message);
    }
}

public class SMSNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("SMS: " + message);
    }
}

public class PushNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("Push: " + message);
    }
}

// Factory Creator
public abstract class NotificationFactory {
    public abstract Notification createNotification();

    public void notifyUser(String message) {
        Notification notification = createNotification();
        notification.send(message);
    }
}

public class EmailFactory extends NotificationFactory {
    @Override
    public Notification createNotification() {
        return new EmailNotification();
    }
}

// Uso:
NotificationFactory factory = new EmailFactory();
factory.notifyUser("Tu pedido ha sido enviado!");
Casos de Uso
Notificaciones Documentos Conexiones BD UI Components Parsers

Builder Pattern

Creational
Intención: Separar la construcción de un objeto complejo de su representación, permitiendo crear diferentes representaciones.
User
- firstName: String (req) - lastName: String (req) - email: String (opt) - age: int (opt)
UserBuilder
+ email(e): UserBuilder + age(a): UserBuilder + build(): User
Java User.java
// Builder Pattern - Fluent Interface
public class User {
    private final String firstName;  // Requerido
    private final String lastName;   // Requerido
    private final String email;      // Opcional
    private final int age;           // Opcional
    private final String phone;      // Opcional

    private User(UserBuilder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.email = builder.email;
        this.age = builder.age;
        this.phone = builder.phone;
    }

    public static class UserBuilder {
        private final String firstName;  // Requerido
        private final String lastName;   // Requerido
        private String email = "";
        private int age = 0;
        private String phone = "";

        public UserBuilder(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }

        public UserBuilder email(String email) {
            this.email = email;
            return this;
        }

        public UserBuilder age(int age) {
            this.age = age;
            return this;
        }

        public UserBuilder phone(String phone) {
            this.phone = phone;
            return this;
        }

        public User build() {
            return new User(this);
        }
    }
}

// Uso con fluent interface:
User user = new User.UserBuilder("Juan", "Pérez")
    .email("juan@example.com")
    .age(28)
    .phone("+56912345678")
    .build();
Casos de Uso
DTOs complejos Queries SQL HTTP Requests Configuraciones Documentos

Adapter Pattern

Structural
Intención: Convertir la interfaz de una clase en otra interfaz que el cliente espera. Permite que clases incompatibles trabajen juntas.
Client
«interface» PaymentProcessor
+ processPayment() + refund()
StripePaymentAdapter
StripeAPI
+ charge() + cancelCharge()
Java StripePaymentAdapter.java
// Adapter Pattern - Integración de APIs externas
// Interfaz que espera nuestro sistema
public interface PaymentProcessor {
    void processPayment(double amount, String currency);
    boolean refund(String transactionId);
}

// API externa con interfaz diferente
public class StripeAPI {
    public String charge(int amountInCents, String cur) {
        // Lógica de Stripe...
        return "txn_" + System.currentTimeMillis();
    }

    public boolean cancelCharge(String chargeId) {
        // Lógica de cancelación...
        return true;
    }
}

// Adapter: adapta Stripe a nuestra interfaz
public class StripePaymentAdapter implements PaymentProcessor {
    private final StripeAPI stripeAPI;

    public StripePaymentAdapter(StripeAPI stripeAPI) {
        this.stripeAPI = stripeAPI;
    }

    @Override
    public void processPayment(double amount, String currency) {
        // Convertimos el monto a centavos
        int amountInCents = (int) (amount * 100);
        stripeAPI.charge(amountInCents, currency);
    }

    @Override
    public boolean refund(String transactionId) {
        return stripeAPI.cancelCharge(transactionId);
    }
}

// Uso: nuestro código usa la interfaz estándar
PaymentProcessor processor = new StripePaymentAdapter(new StripeAPI());
processor.processPayment(99.99, "USD");
Casos de Uso
APIs externas Legacy systems Payment gateways Librerías 3rd party Data formats

Decorator Pattern

Structural
Intención: Añadir responsabilidades adicionales a un objeto dinámicamente. Alternativa flexible a la herencia.
VanillaDecorator
MilkDecorator
SimpleCoffee
= "Café simple, con leche, con vainilla" $2.20
Java CoffeeDecorator.java
// Decorator Pattern - Añadir funcionalidad dinámicamente
public interface Coffee {
    String getDescription();
    double getCost();
}

public class SimpleCoffee implements Coffee {
    @Override
    public String getDescription() {
        return "Café simple";
    }

    @Override
    public double getCost() {
        return 1.00;
    }
}

// Decorador base
public abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }
}

// Decoradores concretos
public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", con leche";
    }

    @Override
    public double getCost() {
        return decoratedCoffee.getCost() + 0.50;
    }
}

public class VanillaDecorator extends CoffeeDecorator {
    public VanillaDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", con vainilla";
    }

    @Override
    public double getCost() {
        return decoratedCoffee.getCost() + 0.70;
    }
}

// Uso: composición dinámica
Coffee miCafe = new VanillaDecorator(
    new MilkDecorator(
        new SimpleCoffee()
    )
);
System.out.println(miCafe.getDescription()); // Café simple, con leche, con vainilla
System.out.println("$" + miCafe.getCost());  // $2.20
Casos de Uso
I/O Streams UI Components Logging Compression Encryption

Observer Pattern

Behavioral
Intención: Definir una dependencia uno-a-muchos entre objetos, de modo que cuando uno cambie de estado, todos sus dependientes sean notificados.
Stock (Subject)
price: $155.00
Inversor1
Inversor2
Dashboard
Java StockObserver.java
// Observer Pattern - Sistema de eventos
import java.util.ArrayList;
import java.util.List;

// Subject (Publisher)
public interface StockSubject {
    void attach(StockObserver observer);
    void detach(StockObserver observer);
    void notifyObservers();
}

// Observer (Subscriber)
public interface StockObserver {
    void update(String stockSymbol, double price);
}

// Concrete Subject
public class Stock implements StockSubject {
    private List observers = new ArrayList<>();
    private String symbol;
    private double price;

    public Stock(String symbol, double price) {
        this.symbol = symbol;
        this.price = price;
    }

    public void setPrice(double price) {
        this.price = price;
        notifyObservers(); // Notifica cambios automáticamente
    }

    @Override
    public void attach(StockObserver observer) {
        observers.add(observer);
    }

    @Override
    public void detach(StockObserver observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for (StockObserver observer : observers) {
            observer.update(symbol, price);
        }
    }
}

// Concrete Observer
public class StockAlert implements StockObserver {
    private String name;

    public StockAlert(String name) {
        this.name = name;
    }

    @Override
    public void update(String stockSymbol, double price) {
        System.out.println(name + ": " + stockSymbol + " ahora vale $" + price);
    }
}

// Uso:
Stock apple = new Stock("AAPL", 150.0);
apple.attach(new StockAlert("Inversor1"));
apple.attach(new StockAlert("Inversor2"));
apple.setPrice(155.0); // Notifica a todos los observers
Casos de Uso
Event systems MVC/MVVM Real-time updates Pub/Sub Reactive programming

Strategy Pattern

Behavioral
Intención: Definir una familia de algoritmos, encapsular cada uno, y hacerlos intercambiables. Permite variar el algoritmo independientemente de los clientes.
ShoppingCart
checkout()
«interface» PaymentStrategy
pay(amount) validate()
CreditCard
PayPal
Crypto
Java PaymentStrategy.java
// Strategy Pattern - Algoritmos intercambiables
public interface PaymentStrategy {
    void pay(double amount);
    boolean validate();
}

public class CreditCardPayment implements PaymentStrategy {
    private String cardNumber;
    private String cvv;

    public CreditCardPayment(String cardNumber, String cvv) {
        this.cardNumber = cardNumber;
        this.cvv = cvv;
    }

    @Override
    public boolean validate() {
        return cardNumber.length() == 16 && cvv.length() == 3;
    }

    @Override
    public void pay(double amount) {
        System.out.println("Pagando $" + amount + " con tarjeta: ****" +
            cardNumber.substring(12));
    }
}

public class PayPalPayment implements PaymentStrategy {
    private String email;

    public PayPalPayment(String email) {
        this.email = email;
    }

    @Override
    public boolean validate() {
        return email.contains("@");
    }

    @Override
    public void pay(double amount) {
        System.out.println("Pagando $" + amount + " via PayPal: " + email);
    }
}

// Context: usa la estrategia sin conocer los detalles
public class ShoppingCart {
    private PaymentStrategy paymentStrategy;

    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }

    public void checkout(double amount) {
        if (paymentStrategy.validate()) {
            paymentStrategy.pay(amount);
        } else {
            throw new RuntimeException("Pago inválido");
        }
    }
}

// Uso: el cliente elige la estrategia
ShoppingCart cart = new ShoppingCart();
cart.setPaymentStrategy(new CreditCardPayment("1234567890123456", "123"));
cart.checkout(99.99);

// Cambiar estrategia en runtime
cart.setPaymentStrategy(new PayPalPayment("usuario@email.com"));
cart.checkout(49.99);
Casos de Uso
Payment processing Sorting algorithms Validation rules Compression Route planning
Data Structures and Algorithms
Knuth, Cormen Tech Interviews Big O

Data Structures

Arrays Hash Tables Trees Graphs Heaps Queues

Algorithms

Sorting Binary Search Dynamic Prog Recursion BFS/DFS Dijkstra

Big O Complexity

O(1)
O(log n)
O(n)
O(n log n)
O(n2)
Software Security (OWASP)
OWASP Top 10 Industry Practices Secure Coding

OWASP Top 10 (2021)

A01Broken Access Control
A02Cryptographic Failures
A03Injection
A04Insecure Design
A05Security Misconfiguration

Secure Coding Practices

Input Validation
Output Encoding
Authentication and Password Management
Session Management
Error Handling and Logging

Arquitectura & DevOps

Patrones de diseño, pipelines y mejores prácticas de la industria

Selecciona una arquitectura para ver detalles

Microservices Architecture Distributed
99.99% Uptime <50ms Latency Auto-Scale
Client
Web Mobile API
API Gateway Load Balancer · Auth · Rate Limit · Circuit Breaker
UsersJWT
ProductsCQRS
OrdersSaga
PaymentsEvents
NotifyPub/Sub
SearchES
Docker K8s Kafka Redis
Best Practices
Un servicio = una responsabilidad Comunicación asíncrona Database per service Circuit Breaker
CI/CD Pipeline DevOps
15 deploys/day 8min build 95% coverage
CodeGit Push
BuildMaven
TestUnit
SecuritySAST
DeployBlue/Green
Actions Jenkins ArgoCD Terraform
Best Practices
Commits pequeños y frecuentes Tests automatizados Rollback automático Infrastructure as Code
Clean Architecture Robert C. Martin
The Dependency Rule

Las dependencias del código fuente solo pueden apuntar hacia adentro. Nada en un círculo interno puede saber nada sobre algo en un círculo externo.

Framework Independent Testable DB Independent
Frameworks & Drivers
Spring JPA REST UI
Interface Adapters
Controllers Presenters Gateways
Application Business Rules
Use Cases Interactors
Enterprise Business Rules
Entities
Dependencies
domain/entity/Order.java Enterprise Business Rules
// Entidad del dominio - sin dependencias externas
public class Order {
    private final OrderId id;
    private final CustomerId customerId;
    private final List<OrderItem> items;
    private OrderStatus status;

    public Order(OrderId id, CustomerId customerId) {
        this.id = id;
        this.customerId = customerId;
        this.items = new ArrayList<>();
        this.status = OrderStatus.CREATED;
    }

    // Business logic dentro de la entidad
    public Money calculateTotal() {
        return items.stream()
            .map(OrderItem::getSubtotal)
            .reduce(Money.ZERO, Money::add);
    }

    public void confirm() {
        if (items.isEmpty()) {
            throw new OrderException("Cannot confirm empty order");
        }
        this.status = OrderStatus.CONFIRMED;
    }
}
application/usecase/CreateOrderUseCase.java Application Business Rules
// Use Case - orquesta la lógica de negocio
public class CreateOrderUseCase {
    private final OrderRepository orderRepository;  // Port
    private final CustomerRepository customerRepository;  // Port
    private final NotificationService notificationService;  // Port

    public CreateOrderUseCase(
            OrderRepository orderRepository,
            CustomerRepository customerRepository,
            NotificationService notificationService) {
        this.orderRepository = orderRepository;
        this.customerRepository = customerRepository;
        this.notificationService = notificationService;
    }

    public OrderOutput execute(CreateOrderInput input) {
        // 1. Validar cliente existe
        Customer customer = customerRepository
            .findById(input.getCustomerId())
            .orElseThrow(() -> new CustomerNotFoundException());

        // 2. Crear entidad Order
        Order order = new Order(
            OrderId.generate(),
            customer.getId()
        );

        // 3. Agregar items
        input.getItems().forEach(item ->
            order.addItem(item.getProductId(), item.getQuantity())
        );

        // 4. Persistir
        orderRepository.save(order);

        // 5. Notificar
        notificationService.sendOrderConfirmation(order);

        return OrderOutput.from(order);
    }
}
infrastructure/adapter/JpaOrderRepository.java Interface Adapters
// Adapter - implementa el Port (Repository)
@Repository
public class JpaOrderRepository implements OrderRepository {

    private final JpaOrderJpaRepository jpaRepository;
    private final OrderMapper mapper;

    public JpaOrderRepository(
            JpaOrderJpaRepository jpaRepository,
            OrderMapper mapper) {
        this.jpaRepository = jpaRepository;
        this.mapper = mapper;
    }

    @Override
    public void save(Order order) {
        OrderJpaEntity entity = mapper.toJpaEntity(order);
        jpaRepository.save(entity);
    }

    @Override
    public Optional<Order> findById(OrderId id) {
        return jpaRepository.findById(id.getValue())
            .map(mapper::toDomain);
    }

    @Override
    public List<Order> findByCustomerId(CustomerId customerId) {
        return jpaRepository
            .findByCustomerId(customerId.getValue())
            .stream()
            .map(mapper::toDomain)
            .collect(Collectors.toList());
    }
}
Estructura de Carpetas
src/main/java
domain
entity/
valueobject/
exception/
application
usecase/
port/ (interfaces)
dto/
infrastructure
adapter/ (impl)
config/
persistence/
Principios Clave (Uncle Bob)
Independiente de Frameworks: No casarse con ningún framework Testable: Reglas de negocio sin UI, DB, o servidor Independiente de UI: La UI puede cambiar sin afectar el sistema Independiente de DB: Oracle, MySQL, Mongo... no importa Independiente de Agencias Externas: Las reglas de negocio no saben del mundo exterior
Medallion Architecture Data Lake
PB Scale Near Real-time BI Ready
Gold Layer Business Aggregates
Transform
Silver Layer Cleaned & Conformed
Clean
Bronze Layer Raw Ingestion
Best Practices
Inmutabilidad en Bronze Schema evolution Particionamiento Data quality checks
Lakehouse Architecture Unified
ACID Time Travel Cost Efficient
BI & Reports
ML & AI
SQL Analytics
Delta Lake / Iceberg / Hudi
Cloud Object Storage (S3/GCS/ADLS)
Key Benefits
Costo de Lake + ACID de DW Streaming + Batch unificado BI y ML sobre mismos datos Open formats (Parquet)
Data Governance Compliance
GDPR/CCPA PII Protected Auditable
Data Catalog
Discovery Metadata Lineage
Data Quality
Validation Profiling Anomaly
Data Security
RBAC Masking Encryption
Observability
Health Freshness Alerts
Governance Framework
Data Stewardship Data Classification Retention Policies Access Controls
Hexagonal Architecture Alistair Cockburn
Ports & Adapters Pattern

Permite que una aplicación sea igualmente dirigida por usuarios, programas, tests automatizados, o scripts batch, y sea desarrollada y probada en aislamiento de sus dispositivos de ejecución y bases de datos.

Loosely Coupled Testable in Isolation Swappable Adapters
Driving Adapters (Primary)
REST Controller HTTP requests
CLI Command line
Web UI User interface
Test Adapter Unit/Integration
Driving Ports (Input)
OrderService PaymentService
Application Core
Entities Use Cases Domain Services
Driven Ports (Output)
OrderRepository NotificationPort
Driven Adapters (Secondary)
PostgreSQL Adapter Database impl
MongoDB Adapter NoSQL impl
SMTP Adapter Email notifications
AWS S3 Adapter File storage
application/port/OrderRepository.java Driven Port (Output)
// Port: Interface que define el contrato
// El dominio NO SABE cómo se implementa
public interface OrderRepository {

    void save(Order order);

    Optional<Order> findById(OrderId id);

    List<Order> findByCustomerId(CustomerId customerId);

    List<Order> findByStatus(OrderStatus status);

    void delete(OrderId id);
}

// Driving Port (Input) - lo que la app expone
public interface OrderService {

    OrderDto createOrder(CreateOrderCommand command);

    OrderDto getOrder(UUID orderId);

    void confirmOrder(UUID orderId);

    void cancelOrder(UUID orderId);
}
adapter/in/web/OrderController.java Driving Adapter (Primary)
// Driving Adapter: Traduce HTTP a llamadas al Port
@RestController
@RequestMapping("/api/orders")
public class OrderController {

    private final OrderService orderService;  // Driving Port

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @PostMapping
    public ResponseEntity<OrderDto> createOrder(
            @Valid @RequestBody CreateOrderRequest request) {

        // Traducir Request → Command
        CreateOrderCommand command = new CreateOrderCommand(
            request.getCustomerId(),
            request.getItems()
        );

        // Llamar al Port
        OrderDto order = orderService.createOrder(command);

        return ResponseEntity
            .status(HttpStatus.CREATED)
            .body(order);
    }

    @GetMapping("/{id}")
    public ResponseEntity<OrderDto> getOrder(@PathVariable UUID id) {
        return ResponseEntity.ok(orderService.getOrder(id));
    }
}
adapter/out/persistence/PostgresOrderRepository.java Driven Adapter (Secondary)
// Driven Adapter: Implementa el Port para PostgreSQL
@Repository
public class PostgresOrderRepository implements OrderRepository {

    private final OrderJpaRepository jpaRepository;
    private final OrderMapper mapper;

    public PostgresOrderRepository(
            OrderJpaRepository jpaRepository,
            OrderMapper mapper) {
        this.jpaRepository = jpaRepository;
        this.mapper = mapper;
    }

    @Override
    public void save(Order order) {
        // Domain → JPA Entity
        OrderEntity entity = mapper.toEntity(order);
        jpaRepository.save(entity);
    }

    @Override
    public Optional<Order> findById(OrderId id) {
        // JPA Entity → Domain
        return jpaRepository.findById(id.getValue())
            .map(mapper::toDomain);
    }

    @Override
    public List<Order> findByCustomerId(CustomerId customerId) {
        return jpaRepository
            .findByCustomerId(customerId.getValue())
            .stream()
            .map(mapper::toDomain)
            .collect(Collectors.toList());
    }
}
Estructura de Carpetas
src/main/java/com/app
adapter/in Driving
web/ (REST controllers)
cli/ (Command line)
messaging/ (Kafka consumers)
adapter/out Driven
persistence/ (JPA, Mongo)
notification/ (Email, SMS)
external/ (Third-party APIs)
application Core
port/in/ (Driving ports)
port/out/ (Driven ports)
service/ (Use cases)
domain Core
model/ (Entities, VOs)
event/ (Domain events)
Hexagonal vs Clean Architecture
Origen Alistair Cockburn (2005) Robert C. Martin (2012)
Enfoque Ports & Adapters Círculos concéntricos
Terminología Driving/Driven, Primary/Secondary Entities, Use Cases, Adapters, Frameworks
Similitud Ambas: Dominio en el centro, independiente de infraestructura
Principios Clave (Alistair Cockburn)
Driving Adapters: Inician la interacción (Controllers, CLI, Tests) Driven Adapters: Son llamados por la aplicación (DB, Email, APIs) Ports: Interfaces que definen cómo comunicarse con el dominio Testability: Reemplazar adapters reales por mocks/stubs Flexibility: Cambiar PostgreSQL por MongoDB sin tocar el dominio
Event-Driven Architecture EDA
Loosely Coupled Real-time Scalable
Event Producers
User Service
Order Service
Payment Service
Event Broker
Kafka EventBridge Pulsar
OrderCreated PaymentProcessed UserRegistered
Event Consumers
Notification
Analytics
Inventory
Event Patterns
Event Sourcing CQRS (Command Query) Saga Pattern Outbox Pattern
Layered Architecture (N-Tier) Traditional
Organized Easy to Learn Separation
1
Presentation Layer UI, Controllers, Views
React Angular REST API
2
Business Logic Layer Services, Use Cases, Domain
Spring .NET Django
3
Data Access Layer Repositories, ORM, DAOs
Hibernate EF Core SQLAlchemy
4
Database Layer Data Storage, Persistence
PostgreSQL MySQL MongoDB
Best Practices (Industry Standard)
Cada capa solo conoce la inferior Separación clara de responsabilidades Ideal para aplicaciones enterprise Fácil de entender y mantener
Domain-Driven Design (DDD) Eric Evans (2003)
Tackling Complexity in the Heart of Software

DDD es un enfoque para desarrollar software complejo conectando la implementación a un modelo evolutivo del dominio del negocio.

Ubiquitous Language Bounded Contexts Aggregates
Building Blocks
Strategic Design
Bounded Context Límite explícito donde el modelo es válido
Ubiquitous Language Lenguaje común entre devs y domain experts
Context Map Relaciones entre bounded contexts
Tactical Design
Entity Objeto con identidad única
Value Object Inmutable, sin identidad
Aggregate Cluster de entidades con root
Repository Abstracción de persistencia
Domain Service Lógica que no pertenece a entidad
Domain Event Algo significativo que ocurrió
domain/model/Order.java Aggregate Root
// Aggregate Root - Order (Entity)
public class Order extends AggregateRoot<OrderId> {
    private final OrderId id;
    private final CustomerId customerId;
    private final List<OrderLine> orderLines;
    private OrderStatus status;
    private Money totalAmount;

    // Factory method - solo forma de crear
    public static Order create(CustomerId customerId) {
        Order order = new Order(
            OrderId.generate(),
            customerId,
            new ArrayList<>(),
            OrderStatus.DRAFT,
            Money.ZERO
        );
        // Emitir Domain Event
        order.registerEvent(new OrderCreatedEvent(order.id));
        return order;
    }

    // Business Logic - protege invariantes
    public void addLine(ProductId productId, int quantity, Money price) {
        if (status != OrderStatus.DRAFT) {
            throw new OrderNotEditableException(id);
        }
        OrderLine line = new OrderLine(productId, quantity, price);
        orderLines.add(line);
        recalculateTotal();
        registerEvent(new OrderLineAddedEvent(id, line));
    }

    public void submit() {
        if (orderLines.isEmpty()) {
            throw new EmptyOrderException(id);
        }
        this.status = OrderStatus.SUBMITTED;
        registerEvent(new OrderSubmittedEvent(id, totalAmount));
    }

    private void recalculateTotal() {
        this.totalAmount = orderLines.stream()
            .map(OrderLine::getSubtotal)
            .reduce(Money.ZERO, Money::add);
    }
}
domain/model/Money.java Value Object
// Value Object - Inmutable, sin identidad
public final class Money {
    public static final Money ZERO = new Money(BigDecimal.ZERO, "USD");

    private final BigDecimal amount;
    private final String currency;

    public Money(BigDecimal amount, String currency) {
        if (amount == null || amount.compareTo(BigDecimal.ZERO) < 0) {
            throw new InvalidMoneyException("Amount cannot be negative");
        }
        this.amount = amount.setScale(2, RoundingMode.HALF_UP);
        this.currency = currency;
    }

    // Operaciones que retornan nuevas instancias
    public Money add(Money other) {
        validateSameCurrency(other);
        return new Money(amount.add(other.amount), currency);
    }

    public Money multiply(int quantity) {
        return new Money(amount.multiply(BigDecimal.valueOf(quantity)), currency);
    }

    // Value Objects se comparan por valor
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Money money = (Money) o;
        return amount.equals(money.amount) && currency.equals(money.currency);
    }

    @Override
    public int hashCode() {
        return Objects.hash(amount, currency);
    }
}
domain/service/PricingService.java Domain Service + Event
// Domain Service - lógica que no pertenece a una entidad
public class PricingService {
    private final DiscountPolicyRepository discountRepo;
    private final TaxCalculator taxCalculator;

    public PricingService(DiscountPolicyRepository discountRepo,
                         TaxCalculator taxCalculator) {
        this.discountRepo = discountRepo;
        this.taxCalculator = taxCalculator;
    }

    // Operación que involucra múltiples agregados
    public Money calculateFinalPrice(Order order, Customer customer) {
        Money subtotal = order.getTotalAmount();

        // Aplicar descuentos según políticas
        DiscountPolicy policy = discountRepo.findForCustomer(customer);
        Money afterDiscount = policy.apply(subtotal, customer);

        // Calcular impuestos según ubicación
        Money taxes = taxCalculator.calculate(afterDiscount, customer.getAddress());

        return afterDiscount.add(taxes);
    }
}

// Domain Event - algo que ocurrió en el dominio
public record OrderSubmittedEvent(
    OrderId orderId,
    Money totalAmount,
    Instant occurredAt
) implements DomainEvent {
    public OrderSubmittedEvent(OrderId orderId, Money totalAmount) {
        this(orderId, totalAmount, Instant.now());
    }
}
Principios Clave (Eric Evans)
Model-Driven Design: El código refleja el modelo del dominio Ubiquitous Language: Todos hablan el mismo idioma Bounded Context: Cada contexto tiene su propio modelo Aggregates: Garantizan consistencia transaccional
CQRS Greg Young
Command Query Responsibility Segregation

Separar las operaciones de lectura (Query) de las de escritura (Command) permite optimizar cada una de forma independiente.

Write Model Read Model Scalable
Command Side (Write)
Command
Command Handler
Domain Model
Write DB
Event Bus Eventual Consistency
Query Side (Read)
Query
Query Handler
Read Model
Read DB
application/command/CreateOrderCommand.java Command + Handler
// Command - intención de cambiar estado
public record CreateOrderCommand(
    UUID customerId,
    List<OrderLineDto> items
) implements Command {}

// Command Handler - ejecuta el comando
@Component
public class CreateOrderCommandHandler implements CommandHandler<CreateOrderCommand> {
    private final OrderRepository orderRepository;
    private final CustomerRepository customerRepository;
    private final EventPublisher eventPublisher;

    @Override
    @Transactional
    public void handle(CreateOrderCommand command) {
        // 1. Validar que cliente existe
        Customer customer = customerRepository.findById(command.customerId())
            .orElseThrow(() -> new CustomerNotFoundException(command.customerId()));

        // 2. Crear agregado Order
        Order order = Order.create(customer.getId());

        // 3. Agregar líneas
        command.items().forEach(item ->
            order.addLine(item.productId(), item.quantity(), item.price())
        );

        // 4. Persistir en Write DB
        orderRepository.save(order);

        // 5. Publicar eventos para actualizar Read DB
        order.getDomainEvents().forEach(eventPublisher::publish);
    }
}
application/query/GetOrderDetailsQuery.java Query + Handler
// Query - solicitud de datos (no modifica estado)
public record GetOrderDetailsQuery(UUID orderId) implements Query {}

// Query Handler - ejecuta la consulta
@Component
public class GetOrderDetailsQueryHandler
        implements QueryHandler<GetOrderDetailsQuery, OrderDetailsDto> {

    private final OrderReadRepository readRepository;  // Read-optimized DB

    @Override
    public OrderDetailsDto handle(GetOrderDetailsQuery query) {
        // Lee de la base optimizada para lecturas
        return readRepository.findById(query.orderId())
            .orElseThrow(() -> new OrderNotFoundException(query.orderId()));
    }
}

// Read Model - optimizado para consultas
@Document(collection = "order_details")
public class OrderDetailsReadModel {
    @Id
    private String orderId;
    private String customerName;
    private String customerEmail;
    private List<OrderLineView> items;
    private String status;
    private BigDecimal totalAmount;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    // Datos denormalizados para evitar JOINs
}
projection/OrderProjection.java Event → Read Model
// Projection - actualiza Read Model cuando ocurren eventos
@Component
public class OrderProjection {
    private final OrderReadRepository readRepository;

    @EventHandler
    public void on(OrderCreatedEvent event) {
        OrderDetailsReadModel model = new OrderDetailsReadModel();
        model.setOrderId(event.orderId().toString());
        model.setStatus("DRAFT");
        model.setCreatedAt(LocalDateTime.now());
        model.setItems(new ArrayList<>());
        readRepository.save(model);
    }

    @EventHandler
    public void on(OrderLineAddedEvent event) {
        OrderDetailsReadModel model = readRepository.findById(event.orderId().toString())
            .orElseThrow();

        model.getItems().add(new OrderLineView(
            event.productId(),
            event.productName(),  // Denormalizado
            event.quantity(),
            event.price()
        ));
        model.setUpdatedAt(LocalDateTime.now());
        readRepository.save(model);
    }

    @EventHandler
    public void on(OrderSubmittedEvent event) {
        OrderDetailsReadModel model = readRepository.findById(event.orderId().toString())
            .orElseThrow();
        model.setStatus("SUBMITTED");
        model.setTotalAmount(event.totalAmount());
        readRepository.save(model);
    }
}
Cuándo Usar CQRS
Alta carga de lectura: Optimizar queries independientemente Modelos complejos: Write y Read tienen necesidades diferentes Escalabilidad: Escalar read/write por separado No siempre: Agrega complejidad, evaluar si es necesario
Event Sourcing Martin Fowler
Events as the Source of Truth

En lugar de almacenar el estado actual, almacenamos todos los eventos que ocurrieron. El estado se reconstruye reproduciendo los eventos.

Full History Time Travel Audit Trail
OrderCreated t0
LineAdded t1
LineAdded t2
Replay Events
Current State
Order #123 Status: SUBMITTED Items: 2 Total: $150.00
domain/Order.java Event Sourced Aggregate
// Event Sourced Aggregate
public class Order extends EventSourcedAggregate {
    private OrderId id;
    private CustomerId customerId;
    private List<OrderLine> orderLines = new ArrayList<>();
    private OrderStatus status;
    private Money totalAmount = Money.ZERO;

    // Constructor desde eventos
    public Order(List<DomainEvent> events) {
        events.forEach(this::apply);
    }

    // Comandos producen eventos (no modifican estado directamente)
    public void createOrder(CustomerId customerId) {
        // Validaciones
        if (customerId == null) {
            throw new InvalidCustomerException();
        }
        // Emitir evento
        raise(new OrderCreatedEvent(OrderId.generate(), customerId, Instant.now()));
    }

    public void addLine(ProductId productId, int quantity, Money price) {
        if (status != OrderStatus.DRAFT) {
            throw new OrderNotEditableException(id);
        }
        raise(new OrderLineAddedEvent(id, productId, quantity, price, Instant.now()));
    }

    // Handlers aplican eventos al estado
    @EventHandler
    private void on(OrderCreatedEvent event) {
        this.id = event.orderId();
        this.customerId = event.customerId();
        this.status = OrderStatus.DRAFT;
    }

    @EventHandler
    private void on(OrderLineAddedEvent event) {
        OrderLine line = new OrderLine(event.productId(), event.quantity(), event.price());
        this.orderLines.add(line);
        this.totalAmount = totalAmount.add(line.getSubtotal());
    }
}
infrastructure/PostgresEventStore.java Event Store
// Event Store - almacena eventos inmutables
public interface EventStore {
    void save(AggregateId id, List<DomainEvent> events, long expectedVersion);
    List<DomainEvent> getEvents(AggregateId id);
    List<DomainEvent> getEvents(AggregateId id, long fromVersion);
}

@Repository
public class PostgresEventStore implements EventStore {
    private final JdbcTemplate jdbc;
    private final ObjectMapper mapper;

    @Override
    @Transactional
    public void save(AggregateId id, List<DomainEvent> events, long expectedVersion) {
        // Optimistic locking
        long currentVersion = getCurrentVersion(id);
        if (currentVersion != expectedVersion) {
            throw new ConcurrencyException(id, expectedVersion, currentVersion);
        }

        long version = expectedVersion;
        for (DomainEvent event : events) {
            version++;
            jdbc.update(
                "INSERT INTO events (aggregate_id, version, type, data, timestamp) VALUES (?, ?, ?, ?, ?)",
                id.toString(),
                version,
                event.getClass().getName(),
                mapper.writeValueAsString(event),
                event.occurredAt()
            );
        }
    }

    @Override
    public List<DomainEvent> getEvents(AggregateId id) {
        return jdbc.query(
            "SELECT * FROM events WHERE aggregate_id = ? ORDER BY version",
            this::mapToEvent,
            id.toString()
        );
    }
}
infrastructure/EventSourcedRepository.java Snapshots
// Snapshots - optimización para agregados con muchos eventos
public interface SnapshotStore {
    Optional<Snapshot> getLatest(AggregateId id);
    void save(Snapshot snapshot);
}

public record Snapshot(
    AggregateId aggregateId,
    long version,
    String state,
    Instant createdAt
) {}

// Repository que usa Event Sourcing + Snapshots
public class EventSourcedOrderRepository implements OrderRepository {
    private final EventStore eventStore;
    private final SnapshotStore snapshotStore;
    private static final int SNAPSHOT_FREQUENCY = 100;

    @Override
    public Optional<Order> findById(OrderId id) {
        // 1. Buscar snapshot más reciente
        Optional<Snapshot> snapshot = snapshotStore.getLatest(id);

        // 2. Cargar eventos desde snapshot o desde el inicio
        List<DomainEvent> events = snapshot
            .map(s -> eventStore.getEvents(id, s.version()))
            .orElse(eventStore.getEvents(id));

        if (events.isEmpty() && snapshot.isEmpty()) {
            return Optional.empty();
        }

        // 3. Reconstruir agregado
        Order order = snapshot
            .map(s -> deserialize(s.state()))
            .orElse(new Order());
        events.forEach(order::apply);

        return Optional.of(order);
    }

    @Override
    public void save(Order order) {
        List<DomainEvent> newEvents = order.getUncommittedEvents();
        eventStore.save(order.getId(), newEvents, order.getVersion());

        // Crear snapshot cada N eventos
        if (order.getVersion() % SNAPSHOT_FREQUENCY == 0) {
            snapshotStore.save(new Snapshot(
                order.getId(),
                order.getVersion(),
                serialize(order),
                Instant.now()
            ));
        }
    }
}
Beneficios de Event Sourcing
Auditoría completa: Sabes exactamente qué pasó y cuándo Time Travel: Reconstruir estado en cualquier punto del tiempo Debug: Reproducir bugs con los mismos eventos Eventos: Base natural para event-driven architecture Complejidad: Más difícil de implementar y mantener

Agile Scrum Framework

Metodología iterativa e incremental para equipos de alto rendimiento

Team 3-9 Sprint 2-4 Weeks Velocity++
SPRINT 2-4 Weeks · Increment
01

Planning

Sprint Goal Definition

4-8 hrs
02

Daily Scrum

Stand-up Meeting

15 min
03

Review

Demo to Stakeholders

2-4 hrs
04

Retrospective

Continuous Improvement

1.5-3 hrs
SCRUM TEAM
Product Owner Backlog Priority
Scrum Master Process Facilitator
Dev Team 3-9 Members
ARTIFACTS
Product Backlog Sprint Backlog Increment Definition of Done
SCRUM VALUES
Commitment Courage Focus Openness Respect