0 / 0 temas completados
Modo examen --:--

Guía de repaso Full-Stack: Angular + NestJS + MikroORM

Temario completo, actualizado y ordenado por prioridad, con plan de estudio de 3 semanas (intensivo) o 4 semanas (equilibrado), ejemplos de código, puntos de examen y 60 preguntas de autoevaluación. Todo el progreso se guarda en tu navegador.

0temas marcables
60preguntas de repaso
3–4semanas de plan
100%offline, un solo archivo

Cómo aprovecharla

  1. Marca cada tema cuando puedas explicarlo en voz alta sin mirar. Si no lo puedes explicar, no lo sabes.
  2. Usa el buscador del panel izquierdo para saltar a un concepto concreto.
  3. Estudia con código: por cada bloque teórico, escribe un mini-ejemplo funcionando. Leer no fija; teclear sí.
  4. Cierra cada día con las preguntas de la sección de autoevaluación relacionadas.
  5. Repaso espaciado: repasa lo del día 1 el día 3, el día 7 y el día 21.

Leyenda de etiquetas

CORE Imprescindible: cae seguro en una evaluación.

MODERNO Añadido reciente del framework; distingue a quien está al día.

Angular NestJS MikroORM Ámbito del tema.

Sobre versiones El ecosistema se mueve rápido (Angular saca versión mayor cada ~6 meses). Esta guía cubre el modelo moderno: componentes standalone, signals, control flow nativo, Nest con módulos y DI, y MikroORM v6 (Unit of Work + Identity Map). Contrasta siempre los detalles de API con la documentación oficial de la versión exacta que uses.

Plan de estudio

Elige el ritmo. El plan de 3 semanas asume ~4 h/día; el de 4 semanas, ~2–2,5 h/día. Ambos terminan con un proyecto integrador, que es lo que de verdad consolida.

Semana 1 — Bases y frontend

Día 1TypeScript avanzado (tipos genéricos, utility types, decoradores, strict) + arquitectura de un proyecto Angular moderno. Crea el esqueleto de la app que usarás toda la guía.
Día 2Angular: componentes standalone, ciclo de vida, input()/output(), comunicación padre-hijo, content projection.
Día 3Signals a fondo: signal, computed, effect, linkedSignal, resource. Detección de cambios y OnPush.
Día 4Plantillas: control flow @if/@for/@switch, @defer, pipes, directivas propias, host.
Día 5Formularios reactivos: validadores síncronos y asíncronos, FormArray, control personalizado con ControlValueAccessor.
Día 6Routing: rutas hijas, lazy loading, guards funcionales, resolvers, parámetros, withComponentInputBinding.
Día 7Repaso + mini-proyecto: CRUD en el frontend con datos simulados. Autoevaluación Angular.

Semana 2 — Backend y persistencia

Día 8NestJS: módulos, controladores, providers, inyección de dependencias, scopes.
Día 9Ciclo de petición completo: middleware → guards → interceptores → pipes → controlador → filtros de excepción. DTOs con class-validator.
Día 10MikroORM: entidades, decoradores, EntityManager, Identity Map, Unit of Work, flush().
Día 11Relaciones: 1:1, 1:N, M:N, Collection, Reference, populate, cascadas, orphan removal.
Día 12Consultas: find con operadores, QueryBuilder, paginación, qb.getResultList(), N+1 y cómo evitarlo.
Día 13Integración Nest + MikroORM: MikroOrmModule, repositorios, RequestContext, transacciones, migraciones y seeders.
Día 14Repaso + API CRUD completa conectada a PostgreSQL. Autoevaluación Nest + ORM.

Semana 3 — Integración, calidad y examen

Día 15Autenticación: JWT, refresh tokens, Passport, guards de roles, hashing con bcrypt/argon2.
Día 16Conectar Angular ↔ Nest: interceptores HTTP, manejo de errores, CORS, tipado compartido.
Día 17Testing: unitario con Jest en Nest, TestBed en Angular, e2e con Supertest.
Día 18Rendimiento: lazy loading, @defer, SSR/hidratación, caché en backend, índices en BD.
Día 19Arquitectura y patrones: capas, DTO vs entidad, repositorio, SOLID, inversión de dependencias, DDD ligero.
Día 20Seguridad OWASP, Docker + docker-compose, variables de entorno, CI básico.
Día 21Simulacro de examen: las 60 preguntas sin mirar + explica tu proyecto en voz alta durante 15 minutos.

Semana 1 — TypeScript + Angular I

L–MTypeScript: tipos, genéricos, utility types, strictNullChecks, decoradores, módulos ES.
X–JAngular: CLI, estructura, componentes standalone, plantillas, binding, ciclo de vida.
VDirectivas, pipes, content projection, comunicación entre componentes.
S/DRepaso activo + primer componente propio no trivial (tabla con filtro y orden).

Semana 2 — Angular II

L–MSignals, computed, effect, estado con signals, OnPush y detección de cambios.
XHttpClient, interceptores funcionales, RxJS operativo (switchMap, debounceTime, catchError).
JFormularios reactivos y validación.
VRouter: lazy loading, guards, resolvers.
S/DMini-app Angular con datos de una API pública. Autoevaluación Angular.

Semana 3 — NestJS

L–MMódulos, DI, providers, configuración, validación con pipes y DTOs.
XGuards, interceptores, filtros de excepción, middleware.
JAutenticación JWT y autorización por roles.
VTesting con Jest y Supertest. Swagger/OpenAPI.
S/DAPI REST completa en memoria. Autoevaluación Nest.

Semana 4 — MikroORM e integración

L–MEntidades, EntityManager, Unit of Work, Identity Map, relaciones.
XConsultas, QueryBuilder, populate, paginación, transacciones.
JMigraciones, seeders, integración con Nest, RequestContext.
VSeguridad, Docker, despliegue y rendimiento.
S/DProyecto integrador Angular + Nest + MikroORM + PostgreSQL y simulacro de examen.

Técnicas que funcionan

  • Recuerdo activo: cierra el material y explica el concepto. Es incómodo, y por eso funciona.
  • Repetición espaciada: 1 día → 3 días → 7 días → 21 días.
  • Técnica Feynman: explícalo como si tu oyente no supiera programar; donde te trabas, ahí está el hueco.
  • Programar sin autocompletado una vez por concepto: obliga a memorizar la API.
  • Depurar a propósito: rompe el código y predice el error antes de ejecutarlo.

Errores típicos al repasar

  • Leer documentación en modo pasivo durante horas sin escribir una línea.
  • Ver vídeos a 2× y sentir que se aprende (no se aprende).
  • Saltarse los fundamentos de JavaScript/TypeScript: la mayoría de los fallos en Angular y Nest son de JS, no del framework.
  • No entender por qué el framework hace algo (DI, change detection, Unit of Work). Eso es justo lo que se pregunta.
  • Estudiar sin base de datos real. Levanta PostgreSQL en Docker desde el día 1.

Proyecto integrador sugerido

Un gestor de tareas de equipo cubre prácticamente todo el temario:

  • Entidades: User, Team, Project, Task, Tag, Comment → cubre 1:1, 1:N y M:N.
  • Auth con JWT + refresh + roles (admin, miembro) → guards y estrategias.
  • Listados con filtros, paginación y ordenación → QueryBuilder y query params validados.
  • Frontend con signals, formularios reactivos, rutas protegidas y carga diferida.
  • Tests unitarios de servicios y un par de e2e; Docker Compose con PostgreSQL; migraciones y seeders.

TypeScript esencial CORE

La base de los tres frameworks. Casi todo error "raro" de Angular o Nest es en realidad un malentendido de tipos o de asincronía.

Sistema de tipos

Lenguaje y configuración

// Type guard + genérico con restricción: patrón que aparece constantemente
interface ApiOk<T> { ok: true; data: T }
interface ApiErr   { ok: false; error: string }
type ApiResult<T> = ApiOk<T> | ApiErr;

function isOk<T>(r: ApiResult<T>): r is ApiOk<T> {
  return r.ok;                      // el compilador estrecha el tipo en el llamador
}

function byId<T extends { id: number }>(items: T[], id: number): T | undefined {
  return items.find(i => i.id === id);
}

// Utility types combinados: DTO derivado de la entidad, sin duplicar tipos
type CreateUserDto = Omit<User, 'id' | 'createdAt'> & { password: string };
Punto de examen Explica por qué emitDecoratorMetadata es necesario para que Nest resuelva dependencias por tipo en el constructor, y qué pasa cuando hay una dependencia circular o una interfaz como token (respuesta: las interfaces no existen en runtime; hay que usar tokens con @Inject()).

Angular · Fundamentos Angular CORE

El modelo moderno: componentes standalone por defecto, inject() en lugar de constructor y configuración por ApplicationConfig.

Estructura y arranque

Componentes

Inyección de dependencias

Directivas y pipes

// Componente standalone moderno: signals de entrada/salida + inject()
@Component({
  selector: 'app-task-card',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [DatePipe],
  template: `
    <article (click)="select.emit(task().id)">
      <h3>{{ task().title }}</h3>
      <p>{{ task().dueDate | date:'shortDate' }}</p>
      @if (isLate()) { <span class="late">Atrasada</span> }
    </article>`,
})
export class TaskCardComponent {
  // entradas como señales: reactivas y tipadas
  task = input.required<Task>();
  compact = input(false, { transform: booleanAttribute });
  select = output<number>();

  private clock = inject(ClockService);
  isLate = computed(() => this.task().dueDate < this.clock.now());
}

Angular · Signals y reactividad Angular MODERNO

El tema estrella de las evaluaciones actuales. Debes saber explicar la diferencia entre el modelo push de signals y el modelo de suscripciones de RxJS.

API de señales

Detección de cambios

// Store con señales: patrón que sustituye a un BehaviorSubject en la mayoría de casos
@Injectable({ providedIn: 'root' })
export class TaskStore {
  private http = inject(HttpClient);

  private _tasks = signal<Task[]>([]);
  filter = signal<'all' | 'open' | 'done'>('all');

  // solo lectura hacia fuera: evita mutaciones desde los componentes
  tasks = this._tasks.asReadonly();

  visible = computed(() => {
    const f = this.filter();
    return f === 'all'
      ? this.tasks()
      : this.tasks().filter(t => (f === 'done' ? t.done : !t.done));
  });
  pending = computed(() => this.tasks().filter(t => !t.done).length);

  add(t: Task) {
    // inmutabilidad: nuevo array, no push, para que la señal notifique el cambio
    this._tasks.update(list => [...list, t]);
  }
}

// Carga asíncrona declarativa: se recarga sola cuando cambia la señal de entrada
const userId = signal(1);
const userRes = resource({
  params:  () => ({ id: userId() }),
  loader:  ({ params, abortSignal }) =>
    fetch(`/api/users/${params.id}`, { signal: abortSignal }).then(r => r.json()),
});
Trampa clásica de examen «¿Por qué mi señal no actualiza la vista si hago tasks().push(x)?» Porque mutas el mismo array: la referencia no cambia y la señal no notifica. Hay que asignar un valor nuevo con set/update.

Angular · Plantillas y control flow Angular

La sintaxis de bloques (@if, @for, @switch, @defer) sustituye a *ngIf y *ngFor.

Sintaxis de plantilla

Comparativa rápida

AntesAhora
*ngIf="a"@if (a) { … }
*ngIf="a; else b"@if (a) {…} @else {…}
*ngFor + trackBy@for (x of xs; track x.id)
[ngSwitch]@switch (v) { @case … }
Lazy manual@defer (on viewport)
@Input()input() señal
@ViewChild()viewChild() señal
Ventaja del control flow nativo Va incluido en el compilador (no hay que importar CommonModule), genera menos código y mejora el rendimiento de las listas frente a *ngFor.
@if (user(); as u) {
  <p>Hola, {{ u.name }}</p>
} @else {
  <app-login />
}

@for (task of visible(); track task.id) {
  <app-task-card [task]="task" />
} @empty {
  <p class="muted">No hay tareas.</p>
}

<!-- Carga el gráfico (pesado) solo cuando entra en pantalla -->
@defer (on viewport) {
  <app-heavy-chart [data]="stats()" />
} @placeholder (minimum 300ms) {
  <div class="skeleton"></div>
} @loading {
  <app-spinner />
} @error {
  <p>No se pudo cargar el gráfico.</p>
}

Angular · Formularios Angular CORE

En una evaluación práctica casi siempre hay un formulario con validación. Domina los reactivos tipados.

Reactivos (prioritario)

Template-driven y comparación

private fb = inject(NonNullableFormBuilder);

form = this.fb.group({
  email:    ['', [Validators.required, Validators.email], [this.emailUnique.validate()]],
  password: ['', [Validators.required, Validators.minLength(8)]],
  confirm:  ['', Validators.required],
  tags:     this.fb.array<string>([]),
}, { validators: samePassword });                // validador a nivel de grupo

// Validador cross-field: recibe el grupo, no el control
const samePassword: ValidatorFn = (g) =>
  g.get('password')!.value === g.get('confirm')!.value ? null : { mismatch: true };

submit() {
  if (this.form.invalid) {
    this.form.markAllAsTouched();     // imprescindible para que se vean los errores
    return;
  }
  this.api.create(this.form.getRawValue()).subscribe();  // getRawValue incluye deshabilitados
}

Angular · Routing Angular

Rutas

Guards y resolvers

export const routes: Routes = [
  { path: '', loadComponent: () => import('./home/home').then(m => m.Home) },
  {
    path: 'admin',
    canMatch: [adminGuard],                    // ni siquiera descarga el bundle si no es admin
    loadChildren: () => import('./admin/routes').then(m => m.ADMIN_ROUTES),
  },
  {
    path: 'tasks/:id',
    loadComponent: () => import('./tasks/detail').then(m => m.TaskDetail),
    resolve: { task: taskResolver },
  },
  { path: '**', component: NotFound },
];

export const adminGuard: CanMatchFn = () => {
  const auth = inject(AuthService), router = inject(Router);
  return auth.isAdmin() ? true : router.createUrlTree(['/login']);
};

Angular · HTTP y RxJS Angular CORE

HttpClient

RxJS operativo

// Interceptor funcional: añade el token y refresca en 401
export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const auth = inject(AuthService);
  const token = auth.token();
  const authReq = token
    ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })
    : req;

  return next(authReq).pipe(
    catchError((err: HttpErrorResponse) =>
      err.status === 401 ? auth.refresh$().pipe(switchMap(() => next(authReq)))
                          : throwError(() => err)),
  );
};

// Buscador con debounce: switchMap cancela la petición anterior
results$ = this.searchControl.valueChanges.pipe(
  debounceTime(300),
  distinctUntilChanged(),
  switchMap(q => this.api.search(q).pipe(catchError(() => of([])))),
);

Angular · Rendimiento, SSR y accesibilidad Angular

Rendimiento

SSR e hidratación

Accesibilidad e i18n

Angular · Testing Angular

Unitario y de componente

E2E y calidad

NestJS · Fundamentos NestJS CORE

Nest es Angular en el servidor: mismo modelo de decoradores, módulos e inyección de dependencias. Si entiendes uno, el otro es casi gratis.

Bloques de construcción

Controladores y HTTP

@Controller('tasks')
export class TasksController {
  constructor(private readonly tasks: TasksService) {}   // DI por tipo

  @Get()
  findAll(@Query() q: FindTasksDto) {
    return this.tasks.findAll(q);           // devolver la promesa: Nest la resuelve
  }

  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return this.tasks.findOne(id);
  }

  @Post()
  @HttpCode(HttpStatus.CREATED)
  create(@Body() dto: CreateTaskDto, @CurrentUser() user: User) {
    return this.tasks.create(dto, user);
  }
}

NestJS · Ciclo de petición NestJS CORE

Pregunta casi garantizada: «¿en qué orden se ejecutan middleware, guards, interceptores, pipes y filtros?».

Orden de ejecución

1. Middleware  →  2. Guards  →  3. Interceptores (antes)  →  4. Pipes  →  5. Manejador del controlador  →  6. Interceptores (después)  →  7. Filtros de excepción (si algo lanza)  →  respuesta.

ElementoPara qué sirveEjemplo típico
MiddlewareAcceso crudo a req/res, antes del enrutadoLogger, helmet, correlación de peticiones
GuardDecidir si la petición puede continuar (autorización)JwtAuthGuard, RolesGuard
InterceptorEnvolver la ejecución: transformar respuesta, medir tiempo, cachearTransformInterceptor, TimeoutInterceptor
PipeTransformar y validar los argumentos de entradaValidationPipe, ParseIntPipe
FiltroCapturar excepciones y formatear la respuesta de errorHttpExceptionFilter, filtro global

Validación y transformación

Errores, guards e interceptores

// Guard de roles: lee metadatos puestos por el decorador @Roles()
@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(ctx: ExecutionContext): boolean {
    const required = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
      ctx.getHandler(), ctx.getClass(),      // método tiene prioridad sobre la clase
    ]);
    if (!required?.length) return true;
    const { user } = ctx.switchToHttp().getRequest();
    return required.some(r => user?.roles?.includes(r));
  }
}

// Filtro global: respuesta de error homogénea para toda la API
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(ex: unknown, host: ArgumentsHost) {
    const res = host.switchToHttp().getResponse();
    const status = ex instanceof HttpException ? ex.getStatus() : 500;
    res.status(status).json({
      statusCode: status,
      message: ex instanceof HttpException ? ex.getResponse() : 'Error interno',
      timestamp: new Date().toISOString(),
    });
  }
}

NestJS · Avanzado NestJS

Rendimiento y tareas

Otros transportes

Organización

NestJS · Autenticación y autorización NestJS CORE

Autenticación

Autorización

@Injectable()
export class AuthService {
  constructor(private users: UsersService, private jwt: JwtService) {}

  async login(email: string, password: string) {
    const user = await this.users.findByEmail(email);
    // mismo mensaje para usuario inexistente y contraseña mala: no filtrar información
    if (!user || !(await bcrypt.compare(password, user.passwordHash)))
      throw new UnauthorizedException('Credenciales inválidas');

    const payload = { sub: user.id, roles: user.roles };
    return {
      accessToken:  await this.jwt.signAsync(payload, { expiresIn: '15m' }),
      refreshToken: await this.jwt.signAsync(payload, { expiresIn: '7d' }),
    };
  }
}

NestJS · Testing NestJS

Unitario / integración

E2E

MikroORM · Fundamentos MikroORM CORE

Lo que distingue a MikroORM de TypeORM/Prisma: implementa Data Mapper, Unit of Work e Identity Map. Entender esos tres conceptos es la mitad del examen.

Conceptos clave

Entidades

@Entity()
export class Task {
  @PrimaryKey()
  id!: number;

  @Property({ length: 180 })
  title!: string;

  @Property({ type: 'text', nullable: true })
  description?: string;

  @Enum(() => TaskStatus)
  status: TaskStatus = TaskStatus.Open;

  @ManyToOne(() => Project, { deleteRule: 'cascade' })
  project!: Project;

  @ManyToMany(() => Tag, tag => tag.tasks, { owner: true })
  tags = new Collection<Tag>(this);

  @Property({ onCreate: () => new Date() })
  createdAt!: Date;

  @Property({ onUpdate: () => new Date(), nullable: true })
  updatedAt?: Date;
}

// Unit of Work en acción: una sola transacción implícita
const task = await em.findOneOrFail(Task, id);
task.title = 'Nuevo título';        // no hay .save(): el UoW detecta el cambio
const otra = em.create(Task, { title: 'Otra', project });
em.remove(vieja);
await em.flush();                      // UPDATE + INSERT + DELETE en un solo flush
Error frecuente Reutilizar el EntityManager raíz entre peticiones concurrentes: el Identity Map se comparte y acabas devolviendo datos de otro usuario. En Nest se resuelve con RequestContext (middleware que hace em.fork() por petición).

MikroORM · Relaciones MikroORM CORE

Tipos de relación

Trabajar con colecciones

// Lado propietario (ManyToOne) e inverso (OneToMany)
@Entity()
export class Project {
  @PrimaryKey() id!: number;

  @OneToMany(() => Task, task => task.project, {
    cascade: [Cascade.PERSIST, Cascade.REMOVE],
    orphanRemoval: true,        // quitar de la colección = borrar la fila
  })
  tasks = new Collection<Task>(this);
}

// Asociar sin SELECT previo: crea una referencia solo con la PK
const projectRef = em.getReference(Project, dto.projectId);
const task = em.create(Task, { title: dto.title, project: projectRef });
await em.flush();

// Evitar N+1: una consulta para las tareas y otra para todas las etiquetas
const tasks = await em.find(Task, { status: 'open' }, {
  populate: ['tags', 'project.owner'],
  strategy: LoadStrategy.SELECT_IN,
});

MikroORM · Consultas MikroORM

EntityManager

QueryBuilder y SQL

// Búsqueda paginada, filtrada y ordenada: patrón de listado de API
const [items, total] = await em.findAndCount(Task,
  {
    project: projectId,
    status: { $in: ['open', 'in_progress'] },
    title: { $ilike: `%${q}%` },
    createdAt: { $gte: from },
  },
  { populate: ['tags'], orderBy: { createdAt: QueryOrder.DESC }, limit, offset },
);

// QueryBuilder para lo que no expresa bien el objeto de condiciones
const rows = await em.createQueryBuilder(Task, 't')
  .select(['t.status', 'count(*) as total'])
  .leftJoin('t.project', 'p')
  .where({ 'p.ownerId': userId })
  .groupBy('t.status')
  .having('count(*) > ?', [3])
  .execute();

MikroORM · Avanzado MikroORM

Transacciones

Esquema y datos

Funciones avanzadas

// Todo dentro de una transacción; si algo lanza, se hace rollback automático
await em.transactional(async (tem) => {
  const account = await tem.findOneOrFail(Account, id, {
    lockMode: LockMode.PESSIMISTIC_WRITE,   // SELECT ... FOR UPDATE
  });
  if (account.balance < amount) throw new ConflictException('Saldo insuficiente');
  account.balance -= amount;
  tem.create(Movement, { account, amount: -amount });
});

// Soft delete con filtro global: se aplica a todas las consultas de la entidad
@Entity()
@Filter({ name: 'notDeleted', cond: { deletedAt: null }, default: true })
export class Document {
  @Property({ nullable: true }) deletedAt?: Date;
}
// Para incluir los borrados: em.find(Document, {}, { filters: { notDeleted: false } })

Integración NestJS + MikroORM NestJS MikroORM CORE

El punto donde se juntan los dos backends y donde más fallos se cometen en la práctica.

Configuración

Buenas prácticas

// app.module.ts
@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    MikroOrmModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (cfg: ConfigService) => ({
        driver: PostgreSqlDriver,
        clientUrl: cfg.getOrThrow('DATABASE_URL'),
        autoLoadEntities: true,
        debug: cfg.get('NODE_ENV') !== 'production',
        // crea un fork del EM por petición: aísla Identity Map y UoW
        registerRequestContext: true,
      }),
    }),
    TasksModule,
  ],
})
export class AppModule {}

// tasks.service.ts
@Injectable()
export class TasksService {
  constructor(
    @InjectRepository(Task) private readonly repo: EntityRepository<Task>,
    private readonly em: EntityManager,
  ) {}

  async update(id: number, dto: UpdateTaskDto): Promise<TaskDto> {
    const task = await this.repo.findOne(id, { populate: ['tags'] });
    if (!task) throw new NotFoundException(`Tarea ${id} no encontrada`);

    this.em.assign(task, dto);      // aplica solo los campos presentes
    await this.em.flush();          // un único flush por caso de uso
    return TaskDto.from(task);
  }
}
Cómo explicarlo en la evaluación «Cada petición HTTP recibe su propio EntityManager gracias a RequestContext. Dentro de ese contexto, el Identity Map garantiza una única instancia por fila y el Unit of Work acumula los cambios; al hacer flush(), MikroORM calcula el diff y ejecuta las sentencias necesarias ordenadas por dependencias, dentro de una transacción implícita.»

SQL y modelado de datos CORE

Un ORM no te libra de saber SQL: te libra de escribirlo a mano. Si no entiendes lo que genera, no puedes optimizarlo.

Consultas

Modelado

Rendimiento

Arquitectura, patrones y buenas prácticas

Diseño

API y contratos

Seguridad web CORE

OWASP y ataques

Prácticas defensivas

Git, Docker y CI/CD

Git

Docker

CI/CD y entorno

# Dockerfile multi-stage para una API Nest: imagen final pequeña
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
CMD ["node", "dist/main.js"]

Examen cronometrado · 20 preguntas tipo test

Usa el botón Modo examen de la barra superior: oculta las respuestas de autoevaluación, muestra solo este test y arranca el cronómetro. Una sola respuesta correcta por pregunta.

1. ¿Qué hace ChangeDetectionStrategy.OnPush?

2. En Nest, el orden correcto es…

3. Identity Map en MikroORM significa…

4. 401 frente a 403…

5. ¿Cuándo es preferible switchMap?

6. timestamptz es adecuado para…

7. El N+1 se mitiga típicamente con…

8. PUT frente a PATCH…

9. Un service worker nuevo suele quedarse en waiting porque…

10. Bloquear el event loop en Node implica…

11. ValidationPipe con whitelist: true

12. Primera regla de ARIA…

13. em.fork() se usa para…

14. Liveness frente a readiness…

15. ETag + If-Match sirven para…

16. Unit of Work…

17. Nx affected

18. Expand/contract en BD…

19. track en @for

20. Idempotency-Key en un POST…

Autoevaluación · 60 preguntas

Responde en voz alta antes de desplegar la respuesta. Si dudas más de 5 segundos, marca el tema como pendiente y vuelve a estudiarlo.

Angular (1–20)

1. ¿Qué es un componente standalone y qué ventaja aporta frente a NgModules?
Es un componente que declara sus propias dependencias en imports sin necesitar un NgModule. Reduce el boilerplate, hace explícitas las dependencias de cada componente, mejora el tree-shaking y simplifica el lazy loading con loadComponent.
2. Diferencia entre signal, computed y effect.
signal es estado escribible; computed es un valor derivado, memoizado y de solo lectura, que se recalcula perezosamente cuando cambian sus dependencias; effect ejecuta efectos secundarios (logging, sincronizar con el DOM o localStorage) cuando cambian las señales leídas dentro. Regla: nunca uses effect para derivar estado, usa computed.
3. ¿Por qué mi señal no refresca la vista si hago miArray().push(x)?
Porque mutas el array existente: la referencia no cambia y la señal no notifica. Hay que crear un valor nuevo: miArray.update(a => [...a, x]).
4. ¿Qué hace ChangeDetectionStrategy.OnPush y cuándo se comprueba el componente?
Le dice a Angular que solo revise el componente cuando cambia la referencia de un input, se dispara un evento en su plantilla, se emite un observable con AsyncPipe, cambia una señal leída en la plantilla o se llama a markForCheck(). Reduce muchísimo el trabajo de detección de cambios.
5. ¿Qué aporta el modo zoneless?
Elimina Zone.js: Angular deja de parchear las APIs asíncronas del navegador y la detección de cambios se dispara por notificaciones explícitas (señales, eventos, markForCheck). Resultado: menos trabajo, bundle más pequeño y rendimiento más predecible. Requiere que el estado que afecta a la vista sea reactivo con señales.
6. ¿Por qué track es obligatorio en @for?
Porque permite a Angular identificar cada elemento entre renders y reutilizar los nodos del DOM en lugar de destruirlos y recrearlos. Sin una clave estable (normalmente el id) se pierde el estado del DOM, el foco y el rendimiento cae en listas grandes.
7. Diferencia entre canActivate y canMatch.
canActivate se evalúa después de que la ruta ha coincidido (y con lazy loading el chunk ya se ha descargado). canMatch se evalúa durante la coincidencia: si devuelve false, la ruta no coincide, no se descarga el bundle y el router prueba la siguiente ruta. Para proteger áreas cargadas de forma diferida, canMatch es mejor.
8. ¿Cuándo usar switchMap, mergeMap, concatMap y exhaustMap?
switchMap: cancela la anterior (buscador en vivo, cambio de ruta). mergeMap: concurrencia total, sin orden garantizado (peticiones independientes). concatMap: en serie y en orden (cola de escrituras). exhaustMap: ignora nuevas emisiones mientras haya una en curso (evitar doble clic en "guardar").
9. ¿Cómo evitas fugas de memoria por suscripciones?
Usando AsyncPipe en la plantilla, takeUntilDestroyed() (con el DestroyRef), take(1) para flujos de un solo valor, o guardando la suscripción y llamando a unsubscribe() en ngOnDestroy. Las peticiones de HttpClient se completan solas, pero el operador sigue pudiendo mantener referencias.
10. ¿Qué es ExpressionChangedAfterItHasBeenCheckedError?
Ocurre en modo desarrollo cuando un valor usado en la plantilla cambia después de que Angular ya lo comprobó en el mismo ciclo (típicamente al modificar estado en ngAfterViewInit o en un hook del hijo). Se resuelve moviendo la lógica a un momento anterior, usando señales o difiriendo el cambio.
11. Formularios reactivos vs template-driven.
Los reactivos definen el modelo en la clase: son síncronos, tipados, fáciles de testear y adecuados para validación compleja y campos dinámicos. Los template-driven se definen con ngModel en la plantilla: más rápidos para formularios triviales, pero asíncronos y difíciles de testear.
12. ¿Cómo creas un componente de input personalizado que funcione con formControlName?
Implementando ControlValueAccessor (writeValue, registerOnChange, registerOnTouched, setDisabledState) y registrándolo con NG_VALUE_ACCESSOR mediante useExisting y multi: true.
13. ¿Para qué sirve @defer?
Para cargar de forma diferida una parte de la plantilla y sus dependencias, con triggers declarativos (on viewport, on idle, on interaction, on hover, on timer, when) y bloques @placeholder, @loading y @error. Reduce el bundle inicial y mejora el tiempo de interacción.
14. ¿Qué diferencia hay entre un pipe puro y uno impuro?
El puro solo se reevalúa cuando cambia la referencia de sus argumentos (comportamiento por defecto y eficiente). El impuro se ejecuta en cada ciclo de detección de cambios, lo que puede matar el rendimiento; solo se justifica en casos muy concretos.
15. ¿Qué es providedIn: 'root' y qué implica para el bundle?
Registra el servicio en el inyector raíz como singleton, sin declararlo en providers. Además permite tree-shaking: si nadie lo inyecta, se elimina del bundle.
16. ¿Cómo se comunican dos componentes hermanos?
Por medio de un servicio compartido con señales o un Subject, elevando el estado al padre común (input/output), o mediante el router/estado global. Evita el acceso directo entre hermanos.
17. ¿Qué hace toSignal() y qué precaución hay que tener?
Convierte un observable en señal y gestiona la suscripción y la limpieza automáticamente. Hay que dar un initialValue (o usar requireSync con un observable síncrono), porque si no el tipo incluye undefined.
18. ¿Qué es la hidratación y qué la rompe?
Es el proceso por el que Angular reutiliza el HTML generado en el servidor en lugar de recrear el DOM en el cliente. La rompen las manipulaciones directas del DOM, HTML inválido y las diferencias de contenido entre servidor y cliente (por ejemplo, usar Date.now() o valores aleatorios en el render).
19. ¿Cómo testeas un componente que llama a una API?
Con TestBed + provideHttpClientTesting(), obteniendo HttpTestingController, esperando la petición con expectOne(), respondiendo con flush(datos) y verificando con verify() que no queden peticiones pendientes.
20. ¿Qué diferencia hay entre ng-container, ng-template y ng-content?
ng-container agrupa sin generar un elemento en el DOM. ng-template define un fragmento que no se renderiza hasta que se instancia (con ngTemplateOutlet o una directiva estructural). ng-content proyecta el contenido que el padre pasa al componente.

NestJS (21–40)

21. Orden de ejecución de middleware, guards, interceptores, pipes y filtros.
Middleware → Guards → Interceptores (parte previa) → Pipes → Manejador del controlador → Interceptores (parte posterior) → Filtros de excepción (si se lanza algo). Los filtros actúan en cualquier punto donde se produzca la excepción.
22. ¿Cómo resuelve Nest las dependencias del constructor?
Con reflect-metadata y emitDecoratorMetadata: TypeScript emite los tipos de los parámetros del constructor y Nest los usa como tokens para buscar el provider en el inyector del módulo. Por eso las interfaces no sirven como token (no existen en runtime) y hay que usar @Inject('TOKEN').
23. ¿Cuándo usarías un guard y cuándo un interceptor?
Guard: decidir si la petición puede continuar (autenticación/autorización); devuelve booleano. Interceptor: envolver la ejecución para transformar la respuesta, medir tiempos, cachear, aplicar timeouts o hacer logging; tiene acceso al flujo antes y después.
24. ¿Qué hace ValidationPipe con whitelist y forbidNonWhitelisted?
whitelist elimina del objeto las propiedades que no tienen decorador de validación (protege contra mass assignment); forbidNonWhitelisted además lanza un 400 si llegan propiedades no permitidas. Con transform: true convierte el payload plano en una instancia del DTO.
25. Diferencia entre @Injectable() con scope DEFAULT, REQUEST y TRANSIENT.
DEFAULT: una única instancia compartida (singleton), lo más eficiente. REQUEST: una instancia por petición, permite estado por petición pero afecta al rendimiento y "contagia" el scope a quien lo inyecta. TRANSIENT: una instancia nueva por cada consumidor.
26. ¿Cómo implementas rutas públicas si tienes un guard JWT global?
Con un decorador @Public() que use SetMetadata (o Reflector.createDecorator) y comprobando esos metadatos con Reflector.getAllAndOverride dentro del guard para devolver true sin validar el token.
27. ¿Qué es un módulo dinámico y para qué sirve?
Un módulo que se configura en tiempo de importación mediante métodos estáticos como forRoot(options) o forRootAsync(), devolviendo un objeto DynamicModule con providers derivados de esas opciones. Es como se configuran ConfigModule, JwtModule o MikroOrmModule.
28. ¿Cómo manejas dependencias circulares entre módulos?
Con forwardRef(() => OtroModule) en los imports y @Inject(forwardRef(() => OtroService)). Pero antes conviene replantear el diseño: la circularidad suele indicar que falta extraer un módulo compartido.
29. ¿Por qué no devolver entidades del ORM directamente en la respuesta?
Porque expones el modelo interno (incluidos campos sensibles como hashes), acoplas la API al esquema de la base de datos, puedes disparar cargas perezosas inesperadas y provocas cambios rompedores en el cliente al modificar la BD. Se devuelven DTOs de respuesta explícitos.
30. ¿Cómo se testea un servicio que depende de un repositorio?
Con Test.createTestingModule proporcionando un doble del repositorio mediante { provide: getRepositoryToken(Entidad), useValue: mock } (o overrideProvider), y verificando tanto el resultado como las llamadas al mock.
31. Diferencia entre 401 y 403.
401 Unauthorized: no hay credenciales válidas (no sabemos quién eres). 403 Forbidden: estás autenticado pero no tienes permiso para ese recurso.
32. ¿Cómo implementarías refresh tokens de forma segura?
Access token de vida corta (5–15 min) y refresh token de vida larga, guardado en cookie httpOnly+Secure+SameSite o almacenado hasheado en la BD. Con rotación en cada uso, detección de reutilización (revocar toda la familia de tokens) y capacidad de revocación por usuario.
33. ¿Qué es un decorador de parámetro personalizado y para qué lo usarías?
Se crea con createParamDecorator y extrae datos del ExecutionContext. El caso típico es @CurrentUser(), que devuelve request.user puesto por el guard, evitando repetir @Req() en cada controlador.
34. ¿Cómo configurarías la aplicación con validación del .env?
Con ConfigModule.forRoot({ isGlobal: true, validationSchema }) usando Joi o Zod, de forma que la app falle al arrancar si falta una variable. Después se accede mediante ConfigService.getOrThrow(), mejor con espacios de nombres tipados (registerAs).
35. ¿Qué diferencia hay entre @MessagePattern y @EventPattern?
@MessagePattern es petición-respuesta: el emisor espera un resultado. @EventPattern es publicación de eventos: dispara y olvida, sin respuesta.
36. ¿Cómo evitas que un endpoint pesado tumbe la API?
Con paginación obligatoria, límites máximos de limit, timeouts (interceptor con timeout()), caché, rate limiting con Throttler, índices adecuados en la BD y moviendo el trabajo pesado a una cola (BullMQ).
37. ¿Qué hace ClassSerializerInterceptor?
Aplica class-transformer a la respuesta, respetando @Exclude() y @Expose(), para ocultar o renombrar campos al serializar. Es una forma rápida de no filtrar campos sensibles, aunque un DTO explícito es más robusto.
38. ¿Cómo estructurarías un módulo de feature?
Carpeta por feature con *.module.ts, *.controller.ts, *.service.ts, dto/, entities/ y *.spec.ts. El módulo exporta solo lo que otros necesitan, e importa MikroOrmModule.forFeature con sus entidades.
39. ¿Cómo se hace un e2e en Nest?
Se crea la app con Test.createTestingModule({ imports: [AppModule] }).compile(), se aplican los mismos pipes y filtros globales que en producción, se llama a app.init() y se lanzan peticiones con Supertest contra app.getHttpServer(), usando una base de datos de test que se limpia entre casos.
40. ¿Cómo devuelves errores coherentes en toda la API?
Con un filtro de excepciones global que normalice el cuerpo (código, mensaje, detalles, timestamp, ruta), mapeando excepciones de dominio y errores del ORM a códigos HTTP adecuados, y registrando los 500 con su traza sin exponerla al cliente.

MikroORM y datos (41–60)

41. ¿Qué es el Identity Map?
Un registro interno del EntityManager que garantiza que, dentro de un contexto, cada fila de la base de datos esté representada por una única instancia en memoria. Dos consultas que devuelven la misma fila devuelven el mismo objeto, lo que evita inconsistencias y consultas repetidas.
42. ¿Qué es el Unit of Work y qué hace flush()?
El UoW acumula todos los cambios (nuevas entidades, modificaciones y borrados) realizados en el contexto. Al llamar a flush(), calcula el diff respecto al estado original, ordena las operaciones respetando las dependencias entre entidades y las ejecuta en una transacción.
43. Data Mapper vs Active Record.
En Active Record la entidad contiene la lógica de persistencia (user.save()). En Data Mapper —el enfoque de MikroORM— las entidades son objetos de dominio puros y el EntityManager se encarga de persistirlas. Esto separa dominio e infraestructura y facilita los tests.
44. ¿Por qué hace falta RequestContext en una app web?
Porque el EntityManager global no es seguro con concurrencia: su Identity Map y su UoW se compartirían entre peticiones simultáneas, mezclando datos de distintos usuarios. RequestContext crea un fork del EM por petición mediante async local storage.
45. ¿Qué es el problema N+1 y cómo se evita en MikroORM?
Es hacer 1 consulta para obtener N registros y luego 1 consulta adicional por cada uno para cargar su relación. Se evita con populate en la consulta principal, eligiendo la estrategia adecuada (select-in hace una consulta extra por relación; joined usa JOIN) o con el QueryBuilder y joinAndSelect.
46. ¿Qué diferencia hay entre em.persist() y em.flush()?
persist() solo marca la entidad para ser gestionada por el UoW; no toca la base de datos. flush() es el que ejecuta el SQL. Las entidades ya gestionadas (obtenidas con find) no necesitan persist(): basta con modificarlas y hacer flush().
47. ¿Para qué sirve em.getReference()?
Para obtener una referencia a una entidad conociendo solo su clave primaria, sin hacer SELECT. Es ideal para asignar relaciones (task.project = em.getReference(Project, id)) y ahorra una consulta.
48. Diferencia entre cascade: [Cascade.REMOVE] y orphanRemoval: true.
Cascade remove borra los hijos cuando se borra el padre. Orphan removal además borra el hijo cuando se le quita de la colección del padre, aunque el padre siga existiendo (relación de composición: el hijo no puede vivir sin padre).
49. ¿Cómo implementas soft delete?
Con una columna deletedAt nullable y un @Filter global por defecto con condición { deletedAt: null }. "Borrar" es asignar la fecha; para consultar los borrados se desactiva el filtro con { filters: { notDeleted: false } }.
50. ¿Cuál es el lado propietario de una relación y por qué importa?
Es el lado que guarda la clave foránea (siempre el @ManyToOne, o el que declara owner: true en 1:1 y M:N). Importa porque los cambios solo se persisten si se hacen en el lado propietario; modificar únicamente la colección inversa no genera SQL.
51. ¿Cómo haces una transacción que abarque varias operaciones?
Con em.transactional(async (em) => { … }): hace commit al terminar sin errores y rollback si se lanza una excepción. También existe el control manual con begin/commit/rollback y el decorador @CreateRequestContext para contextos fuera de una petición HTTP (cron, colas).
52. ¿Qué es el bloqueo optimista y cómo se activa?
Con una propiedad de versión (@Property({ version: true })): al hacer UPDATE se comprueba que la versión no haya cambiado; si otro proceso la modificó, se lanza un error de concurrencia. Evita perder actualizaciones sin bloquear filas.
53. ¿Por qué no usar schema:update en producción?
Porque compara y modifica el esquema automáticamente, y puede generar operaciones destructivas (borrar columnas o tablas) o bloqueos largos. En producción se usan migraciones versionadas, revisadas y reversibles, ejecutadas como paso del despliegue.
54. ¿Cómo se pagina correctamente un listado grande?
Con findAndCount y limit/offset para casos normales (devolviendo items y total). Para conjuntos muy grandes, paginación por cursor/keyset (WHERE id < ultimoId ORDER BY id DESC LIMIT n), porque el OFFSET alto es costoso.
55. ¿Cuándo usarías el QueryBuilder en lugar de em.find()?
Cuando necesitas agregaciones, GROUP BY/HAVING, joins complejos con condiciones, subconsultas, expresiones SQL o proyecciones que no se corresponden con entidades completas.
56. ¿Qué diferencia hay entre em.nativeUpdate() y modificar la entidad + flush()?
nativeUpdate ejecuta un UPDATE directo, sin pasar por el UoW: es rápido para actualizaciones masivas pero no dispara hooks, no actualiza el Identity Map (las entidades en memoria quedan desactualizadas) y no respeta la versión.
57. ¿Cómo diseñarías una relación muchos-a-muchos con datos extra?
Con una entidad pivote explícita (por ejemplo TaskAssignment con task, user, role y assignedAt) y dos relaciones @ManyToOne, en lugar de un @ManyToMany automático, porque la tabla intermedia necesita atributos propios.
58. ¿Qué índices crearías para un listado filtrado por proyecto y ordenado por fecha?
Un índice compuesto (project_id, created_at DESC): la columna de igualdad primero y la de ordenación después, para que el motor pueda filtrar y devolver ya ordenado sin un sort adicional.
59. ¿Cómo se prueba código que usa MikroORM?
En tests unitarios, con dobles del repositorio o del EM. En integración, con una base de datos real (SQLite en memoria o PostgreSQL con Testcontainers), inicializando el esquema con orm.schema.refreshDatabase() y aislando cada test con transacciones que se revierten o con limpieza entre casos.
60. Explica el flujo completo de una petición PATCH /tasks/1 en tu stack.
El cliente Angular envía la petición; el interceptor añade el token. En Nest: middleware (logger, RequestContext con el fork del EM) → guard JWT y de roles → interceptores → ValidationPipe valida y transforma el DTO → el controlador delega en el servicio → el servicio carga la entidad (Identity Map), aplica em.assign, y hace flush(), momento en el que el UoW genera el UPDATE dentro de una transacción → se mapea a DTO de respuesta → el interceptor la serializa → si algo falla, el filtro de excepciones devuelve un error normalizado. En el cliente se actualiza la señal del store y la vista se refresca.

Recursos y checklist final

Documentación oficial

Checklist de "estoy listo"

Último consejo En una evaluación oral, lo que más puntúa no es recitar la API, sino explicar por qué el framework funciona así y qué problema resuelve. Prepara una explicación de 2 minutos para cada uno de estos tres conceptos: inyección de dependencias, reactividad con señales y Unit of Work. Con eso demuestras que entiendes el modelo mental completo del stack.

Guía de repaso Angular · NestJS · MikroORM — tu progreso se guarda automáticamente en este navegador.