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.
Cómo aprovecharla
- Marca cada tema cuando puedas explicarlo en voz alta sin mirar. Si no lo puedes explicar, no lo sabes.
- Usa el buscador del panel izquierdo para saltar a un concepto concreto.
- Estudia con código: por cada bloque teórico, escribe un mini-ejemplo funcionando. Leer no fija; teclear sí.
- Cierra cada día con las preguntas de la sección de autoevaluación relacionadas.
- 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.
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
strict) + arquitectura de un proyecto Angular moderno. Crea el esqueleto de la app que usarás toda la guía.input()/output(), comunicación padre-hijo, content projection.signal, computed, effect, linkedSignal, resource. Detección de cambios y OnPush.@if/@for/@switch, @defer, pipes, directivas propias, host.FormArray, control personalizado con ControlValueAccessor.withComponentInputBinding.Semana 2 — Backend y persistencia
class-validator.EntityManager, Identity Map, Unit of Work, flush().Collection, Reference, populate, cascadas, orphan removal.find con operadores, QueryBuilder, paginación, qb.getResultList(), N+1 y cómo evitarlo.MikroOrmModule, repositorios, RequestContext, transacciones, migraciones y seeders.Semana 3 — Integración, calidad y examen
TestBed en Angular, e2e con Supertest.@defer, SSR/hidratación, caché en backend, índices en BD.Semana 1 — TypeScript + Angular I
strictNullChecks, decoradores, módulos ES.Semana 2 — Angular II
computed, effect, estado con signals, OnPush y detección de cambios.HttpClient, interceptores funcionales, RxJS operativo (switchMap, debounceTime, catchError).Semana 3 — NestJS
Semana 4 — MikroORM e integración
populate, paginación, transacciones.RequestContext.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 };
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()),
});
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
| Antes | Ahora |
|---|---|
*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 |
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.
| Elemento | Para qué sirve | Ejemplo típico |
|---|---|---|
| Middleware | Acceso crudo a req/res, antes del enrutado | Logger, helmet, correlación de peticiones |
| Guard | Decidir si la petición puede continuar (autorización) | JwtAuthGuard, RolesGuard |
| Interceptor | Envolver la ejecución: transformar respuesta, medir tiempo, cachear | TransformInterceptor, TimeoutInterceptor |
| Pipe | Transformar y validar los argumentos de entrada | ValidationPipe, ParseIntPipe |
| Filtro | Capturar excepciones y formatear la respuesta de error | HttpExceptionFilter, 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
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);
}
}
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.
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?
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)?
miArray.update(a => [...a, x]).4. ¿Qué hace ChangeDetectionStrategy.OnPush y cuándo se comprueba el componente?
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?
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?
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?
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?
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.
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?
ControlValueAccessor (writeValue, registerOnChange, registerOnTouched, setDisabledState) y registrándolo con NG_VALUE_ACCESSOR mediante useExisting y multi: true.13. ¿Para qué sirve @defer?
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?
15. ¿Qué es providedIn: 'root' y qué implica para el bundle?
providers. Además permite tree-shaking: si nadie lo inyecta, se elimina del bundle.16. ¿Cómo se comunican dos componentes hermanos?
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?
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?
Date.now() o valores aleatorios en el render).19. ¿Cómo testeas un componente que llama a una API?
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.
22. ¿Cómo resuelve Nest las dependencias del constructor?
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?
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.
26. ¿Cómo implementas rutas públicas si tienes un guard JWT global?
@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?
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?
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?
30. ¿Cómo se testea un servicio que depende de un repositorio?
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.
32. ¿Cómo implementarías refresh tokens de forma segura?
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?
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?
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?
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?
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?
*.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?
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?
MikroORM y datos (41–60)
41. ¿Qué es el Identity Map?
42. ¿Qué es el Unit of Work y qué hace flush()?
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.
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?
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?
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()?
task.project = em.getReference(Project, id)) y ahorra una consulta.48. Diferencia entre cascade: [Cascade.REMOVE] y orphanRemoval: true.
49. ¿Cómo implementas soft delete?
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?
@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?
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?
@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?
54. ¿Cómo se pagina correctamente un listado grande?
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()?
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?
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?
(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?
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.
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
- angular.dev — guía y tutoriales actualizados (signals, control flow, SSR).
- docs.nestjs.com — capítulos «Overview», «Fundamentals» y «Techniques».
- mikro-orm.io/docs — sobre todo «Unit of Work», «Identity Map» y «Relationships».
- rxjs.dev — el decision tree de operadores.
- TypeScript Handbook — genéricos y tipos avanzados.
- OWASP Top 10 — repaso rápido antes del examen.
Checklist de "estoy listo"
Guía de repaso Angular · NestJS · MikroORM — tu progreso se guarda automáticamente en este navegador.