Preparación de entrevista — Full Stack Developer (Angular + .NET)

Empresa: NEWCO LLC · marca Vita Tienda de Cocó March · Remoto · Candidato: Roy García
Oferta Computrabajo 05F54C…3405 Salario: a convenir Prep generada 2026-08-28 Fuente clave: notas del entrevistador

Resumen ejecutivo

Formato confirmado — martes 8 sep, 8:00 am CDMX.

En vivo, pantalla compartida, tu editor. Construyes 3 piezas: (1) una consulta SQL a mano, (2) un método de servicio .NET que la ejecuta, (3) el endpoint que la expone. Más una parte hablada de Angular. Guion completo en la pestaña La prueba.

Lo que NO necesitas: proyecto suyo, su base de datos, compilar, ejecutar. Un .cs suelto basta. Te dan las firmas de sus clases por chat — no se mide memoria. Sin documentación, internet ni IA: apaga Copilot / la extensión de Claude antes de entrar.

Qué juega a tu favor

Qué van a apretar

  1. SQL Server — join + fecha + paginación + transacción (decide todo).
  2. Angular a fondo — Observable vs Promesa; qué te aportó y qué te costó Native Federation.
  3. Karma/Jasmine ejercido — el último test de componente que escribiste y qué comprobaba.
  4. AWS Lambda → Azure — qué cambia; buena señal si nombras lo que no equivale.
  5. Inglés — te lo van a preguntar sin rodeos (docs de SP-API, Stripe, Angular en inglés).
  6. Historia laboral — un solo empleador, pasante sin titular desde 2021.

5 focos de estudio priorizados

#FocoPor quéDónde
1Joins + paginación (OFFSET/FETCH y keyset) + transacciones en SQL ServerEs la pregunta que decidePestaña SQL
2Acceso a datos "a mano": Dapper / ADO.NET con IDbTransaction"La mitad del trabajo" según ellosPestaña SQL
3AWS→Azure: tabla de equivalencias y no-equivalenciasPregunta 4, tu punto débil de cloudPestaña Cloud
4Respuesta honesta y ensayada sobre tu inglésPregunta 5, la van a testearPestaña Preguntas
5Native Federation: beneficios y dolores concretos, poder dibujarloPregunta 2, tu fuerte — hay que lucirloPestaña Angular

Red de seguridad: si no pasa la prueba de SQL, sigues siendo candidato fuerte para el puesto de Front End Developer que NEWCO también tiene abierto. No es un fracaso — pero el objetivo es Full-Stack.

Hoy es jueves 3 — según tu propio plan (pestaña Plan al 8 sep), hoy toca "las 3 piezas completas + la voz". Empieza por Memoriza esto — es la versión de una pantalla de lo que vas a teclear el martes, con los trucos para que se quede pegado. La prueba es donde vive el porqué de cada línea; vuelve ahí solo cuando algo no cuadre.

Memoriza esto — la estructura en una pantalla

El mismo guion de La prueba, comprimido a lo que de verdad tienes que traer en la cabeza el martes. El razonamiento completo de cada línea sigue viviendo allá — aquí solo el esqueleto y cómo clavarlo.

Por qué te cuesta memorizarlo: no es un texto, son 6 decisiones técnicas encadenadas. Un texto se memoriza repitiéndolo; una decisión se memoriza entendiendo qué pasa si la tomas al revés. Por eso cada bloque de abajo trae su "si no lo haces así, se rompe esto" — ese es el gancho que se queda, no la sintaxis.

1 · El archivo, de arriba a abajo — tu mapa

Todo el ejercicio son 5 bloques que caen en el mismo orden siempre. Si memorizas el orden, cada bloque te recuerda al siguiente — es la misma "costura" que ya usas en SEUS (Interfaz→Service→Assembly→Promise), un peldaño más largo.

  1. Encabezadotus 3 tablas comentadas + el hueco para las firmas del chat. Lo primero que escribes, sin pensar.
  2. La consulta SQLen un bloque /* */, en español antes que en C#: SELECT → FROM+JOIN → WHERE → ORDER BY → OFFSET → COUNT. La pieza que decide.
  3. El método de serviciomueves ese mismo SQL a un const string, abres conexión, QueryMultipleAsync, lees lista + total.
  4. El endpoint[HttpGet], validas el rango (400), saneas paginación, delegas al servicio, Ok(result).
  5. Los tiposal final, solo si no te los dieron por chat: el DTO de fila y el PagedResult<T>.

2 · La consulta, en 6 frases — tu retrieval practice

No memorices el SQL carácter por carácter. Memoriza estas 6 frases; el código sale solo de decir la frase en voz alta mientras tecleas — es exactamente la técnica de doble codificación (sección 4 abajo).

#Frase que dicesSi lo haces al revés…
1"Traigo columnas con alias claros; el total lo calculo, no lo guardo."el DTO no mapea, o guardas un dato derivable que se desincroniza.
2"Empiezo por la tabla más fina (OrderLine) y le cuelgo el resto con INNER JOIN."arrancar por la tabla "grande" te obliga a pensar el join al revés.
3"Rango semiabierto: >= @from AND < @toExclusive, nunca BETWEEN."BETWEEN con horas pierde el último día completo. Es el bug que buscan.
4"ORDER BY que termina en una columna única — OFFSET/FETCH lo exige."sin desempate único, la página 2 repite o se salta filas.
5"Pagino con OFFSET/FETCH; para scroll infinito usaría keyset."nombrar solo OFFSET sin saber su límite suena a que no sabes por qué existe keyset.
6"El COUNT lleva el mismo WHERE, sin el join que no necesita."si el COUNT no comparte el filtro, el paginador miente sobre cuántas páginas hay.

3 · Las 3 preguntas que SIEMPRE llegan después

Te preguntan…Tu ancla de una línea
"¿Y la transacción?""Esto es un SELECT, no la necesita. La necesitaría en una escritura de varias tablas: crear pedido + renglones + descontar stock."
"¿Y si son 100,000 filas?""OFFSET grande relee y descarta; para eso, keyset — filtro por la última fila vista."
"¿Angular / Native Federation?"Aportó: deploy independiente. Costó: version skew — dos Angular, NG0203.

4 · Cómo se queda pegado (esto sí es la clave, no el SQL)

1 · Recuperación activa, no relectura. Leer la pestaña La prueba otra vez se siente productivo y no lo es. Lo que funciona: reset-prueba.bat, pantalla en blanco, recitas de memoria antes de mirar. Fallar y corregir es lo que fija el recuerdo — leer sin fallar no deja rastro.

2 · Dilo y tecléalo a la vez (doble codificación). La voz usa una memoria distinta a la de los dedos. Por eso cada paso de La prueba trae su frase exacta: si solo tecleas en silencio, memorizas sintaxis suelta; si narras mientras tecleas, memorizas una decisión con su razón — eso es lo que no se te olvida bajo presión.

3 · Trocea en 6, no en 40. Nadie memoriza 40 líneas de un tirón; la mente sí retiene 6 bloques con nombre (tabla de arriba). Cuando se te vaya una línea en vivo, no busques la línea — pregúntate "¿en qué bloque de los 6 estoy" y esa frase te devuelve el código.

4 · Intercala, no repitas lo mismo 3 horas. SQL → Angular → guion oral → SQL otra vez rinde más que 3 horas seguidas de SQL. El cerebro fija mejor cuando alterna que cuando satura un solo tema (ya está así de dividido en tu Plan: hoy piezas+voz, sáb-dom Angular, lun simulacro).

5 · Enseña el porqué a un principiante. Si puedes explicarle a alguien que nunca vio SQL por qué no usas BETWEEN, lo tienes de verdad. Memorizar sintaxis se olvida con los nervios; entender el porqué no.

La meta de hoy y mañana (tu propio plan, pestaña Plan al 8 sep): las 3 piezas completas + repaso en voz en 12–15 min, sin mirar. Usa esta pestaña para el repaso rápido de cada corrida; usa La prueba solo la primera vez o cuando un bloque no te salga.

La prueba — guion completo

Martes 8 sep, 8:00 am CDMX. Pantalla compartida, tu editor, un fichero suelto. No compila nada. Se valora el código y lo que dices mientras lo escribes.

El correo de Alberto, en claro:

  • Construyes 3 cosas: consulta SQL a mano · método de servicio .NET que la ejecuta · endpoint que la expone.
  • Parte hablada sobre Angular.
  • Sin Entity Framework. Sin procedimientos almacenados. SQL a mano.
  • Fichero suelto o proyecto tuyo. No tendrás su base de datos. No hace falta compilar ni ejecutar.
  • El dominio y las tablas los eliges tú — no te dan un enunciado de negocio.
  • Pero las firmas de sus clases (DTO, interfaz de servicio, tipo de resultado, objeto de query) te las dan por el chat y contra ESAS implementas. No se mide memoria. Cómo se maneja eso → sección "Cuando te pasen las firmas" más abajo.
  • Nada de documentación, internet ni IA. Apaga Copilot / la extensión de Claude antes.
  • Piensa en voz alta. Buena conexión y audio.

Cómo hablas — 3 reglas (feedback de tu ex jefe, aplican a TODO lo hablado).

  1. Seguridad — nada de "creo", "siento", "pienso", "me parece", "no estoy seguro pero". Esas muletillas dicen "no tengo experiencia real en esto".
    • "Creo que se hace con…" → "Se hace con…"
    • "Yo pienso que sería mejor…" → "Aquí conviene X porque…"
    • Si de verdad no lo sabes: dilo con firmeza"No lo he usado en producción; por lo que sé es X, lo confirmaría [cómo]." Mejor eso que un "creo" tembloroso.
  2. Contexto — cada respuesta va situada, nunca en abstracto. Ancla en dónde lo usaste, qué problema resolvía, qué decidiste.
    • Flojo: "switchMap cancela la petición anterior."
    • Fuerte: "En el buscador de producto de SEUS, cada tecla dispara una consulta; con debounceTime y un hash de petición descartamos las respuestas viejas."
    • Formato mental: [situación] → [problema] → [qué hice] → [resultado / por qué].
  3. Lenguaje técnico — nombra cada cosa por su nombre.
    • "esa cosa que evita recargar" → change detection · señal · OnPush
    • "lo que conecta los módulos" → el federation.manifest.json · el import map
    • "un candado en la base" → aislamiento SERIALIZABLE · un bloqueo de rango
    • Los términos exactos: SARGable, determinista, idempotente, atómico, keyset, round-trip, primary constructor, tree-shaking, standalone, signals-first.

Prepara tu ejemplo AHORA — no lo inventes en vivo. El dominio y las tablas los eliges tú; las firmas de código te las dan por chat y te adaptas (sección abajo). Llega con el esquema decidido y practicado: un reporte con JOIN de 3 tablas + filtro de fecha + paginación. Mantenlo mínimo — 3 tablas, sin adornos:

[Order]    (OrderId PK, OrderDate datetime2, Status)
Product    (ProductId PK, Sku, Name)
OrderLine  (OrderLineId PK, OrderId FK→[Order], ProductId FK→Product, Quantity, UnitPrice)

Al empezar, tú lo anuncias: "Voy a hacer un reporte de líneas de pedido en un rango de fechas, paginado. Contra estas tres tablas —las dibujo—: un pedido, sus líneas, y el producto de cada línea. Si prefieren otra cosa, díganme."

El ejemplo ya está montado con datos reales — para practicar la consulta contra una BD de verdad (en la prueba no la tendrás, pero para ensayar sí ayuda):

  • datos-prueba.sql → crea la BD PruebaCocoMarch en localhost\SQLEXPRESS con las 3 tablas y datos de Vita Tienda (10 productos, 20 pedidos entre dic-2025 y feb-2026, 37 líneas). Ya ejecutado. Re-crear: sqlcmd -S "localhost\SQLEXPRESS" -E -C -i datos-prueba.sql
  • verificar.sql → la consulta de la prueba corriendo, más la demo del rango semiabierto (pedido del 31-ene 23:30 entra, el del 1-feb 00:00 no) y de la paginación determinista.
  • prueba.cs (Escritorio) → arranca con el esqueleto en blanco (encabezado + using + los 3 marcadores). El objetivo completo está en "El archivo completo" abajo.
  • Bucle de práctica: doble clic en reset-prueba.bat → deja prueba.cs en blanco y lo abre en VS Code. Tecleas la solución narrando, ejecutas verificar.sql para comprobar, y repites. reset-bd.bat recrea la BD si ensayaste las variaciones de INSERT.
  • Connection string para ensayar: Server=localhost\SQLEXPRESS;Database=PruebaCocoMarch;Trusted_Connection=True;TrustServerCertificate=True

Datos pensados para lucir el filtro: con fromDate = 2026-01-01 y toExclusive = 2026-02-01 salen 22 líneas de enero; diciembre y febrero quedan fuera, y el pedido a medianoche del 1-feb demuestra por qué < @toExclusive y no BETWEEN.

Paso 0 · Antes de conectarte T-15 min

0.1 · Apaga los asistentes de IA

Haces: VS Code ▸ Ctrl+Shift+X ▸ escribe "Claude" ▸ en Claude Code pulsa Disable. Repite con "Copilot" si aparece. Luego Ctrl+Shift+P ▸ "Reload Window".

Por qué: Alberto lo pide explícito y en el screen share se nota si un autocompletado fantasma te sugiere líneas enteras. Que no haya ninguna duda de que el código es tuyo.

Truco: conviértelo en el primer gesto físico de tu ritual de entrada — como abrocharte el cinturón. Hazlo siempre en cada ensayo, en el mismo orden, y el martes las manos lo hacen solas sin que lo pienses.

0.2 · Crea el fichero

Haces: Ctrl+N (archivo nuevo) ▸ Ctrl+S ▸ guárdalo como prueba.cs en el Escritorio.

Por qué: "un fichero suelto" es literal — no abras carpeta, no crees proyecto, no hay que compilar. La extensión .cs es solo para que VS Code lo coloree y se lea ordenado en pantalla.

Truco: practica siempre con el mismo reset-prueba.bat — ver el archivo en blanco una y otra vez es la señal que tu cerebro asocia con "ahora toca recitar sin mirar".

0.3 · Distribuye la pantalla

Haces: el editor ocupando ~⅔, y la ventana de la reunión con el chat abierto en el ⅓ restante (o en un segundo monitor). Ahí van a pegar las firmas.

Por qué: si tienes que Alt+Tab cada vez que pegan algo, pierdes el hilo y se te ve titubear. Con el chat siempre a la vista, copias y sigues.

Truco: ensaya con esta misma distribución de pantalla desde ya — el cerebro asocia el layout con la tarea, y llegar a un layout ya familiar el martes quita un nervio de encima.

0.4 · Audio y prueba técnica

Haces: auriculares con micro (no el del portátil). Entra 2 min antes, comparte una pantalla de prueba, confirma que se te oye.

Por qué: "si te quedas en silencio no podemos valorar lo que haces" — el audio es la evaluación. Un corte de sonido a mitad te cuesta la prueba.

Truco: en cada corrida de práctica grábate la voz (ya lo pide la Corrida 3 de tu plan) — oírte a ti mismo narrando es lo que te va a decir si de verdad puedes sostener 15 minutos hablando sin apagones.

Paso 1 · El arranque primeros 2 min

1.1 · Reformula la tarea

Dices: "Perfecto. Entonces necesitan tres cosas: una consulta SQL a mano, un método de servicio en .NET que la ejecute, y el endpoint que la expone. Trabajo en este fichero suelto y les voy narrando. ¿Correcto?"

Por qué: confirmas que entendiste, fijas el marco, y les cedes el turno para matizar antes de teclear nada.

Truco: es la única frase que puedes dejar casi memorizada palabra por palabra — di siempre las mismas 3 piezas en el mismo orden (SQL · método · endpoint) y se vuelve automática de tanto repetirla igual en cada ensayo.

1.2 · Anuncia tu ejemplo

Dices: "Como el dominio lo elijo yo, voy con un reporte de líneas de pedido en un rango de fechas, paginado — ejercita bien el JOIN, el filtro y la paginación. Contra tres tablas: un pedido, sus líneas, y el producto de cada línea. Si prefieren otra cosa, díganme."

Por qué: propones, no preguntas "¿qué quieren?". Eliges un caso realista y explicas por qué luce lo que evalúan. Dejas la puerta abierta por si tienen preferencia.

Truco: usa siempre el mismo ejemplo en cada ensayo — pedido/línea/producto. Cambiar de dominio cada vez que practicas es la razón número uno por la que "se te olvida": no repites el guion, inventas uno nuevo cada vez.

1.3 · Teclea el encabezado del fichero

Escribes:

// Prueba tecnica - reporte de lineas de pedido por rango de fechas, paginado
//
// Mis 3 tablas:
//   [Order]   (OrderId PK, OrderDate, Status)
//   Product   (ProductId PK, Sku, Name)
//   OrderLine (OrderLineId PK, OrderId -> [Order], ProductId -> Product, Quantity, UnitPrice)
//
// Firmas que me pasen por el chat:
//   (pegar aqui)

Dices: "Dejo el esquema escrito para tenerlo a la vista, y un hueco para las firmas que me pasen."

Por qué: el esquema en pantalla te ancla y les enseña el modelo. El hueco evita que pierdas las firmas en el scroll del chat.

Truco: tecléalo tú a mano en cada práctica, nunca lo copies. Son 5 líneas cortas — el punto no es ahorrarte 10 segundos, es que teclearlo es lo que lo fija (doble codificación, pestaña Memoriza esto).

1.4 · Preguntas para acotar (elige 2)

  • "¿La paginación la esperan base 0 o base 1?"
  • "El total para el paginador, ¿lo quieren en la misma respuesta?"
  • "¿Me pasan ya las firmas de los tipos, o los defino yo y los ajusto si hace falta?"

Por qué: traduces un requerimiento a una spec — es literal lo que pide el puesto. Y te ahorra rehacer a mitad.

Truco: no memorices las 3 preguntas literales — memoriza que siempre preguntas 2 antes de teclear. El hábito importa más que el texto exacto; en vivo puedes improvisar la redacción si tienes el reflejo de parar y preguntar.

Dónde trabajas de aquí en adelante: un solo archivo, prueba.cs, abierto en VS Code. Nada más. No abres SSMS, no ejecutas nada, no hay proyecto — el correo lo dice: "no hace falta que compiles ni ejecutes". (SSMS y la BD PruebaCocoMarch fueron solo para tu práctica de estos días.)

La consulta SQL se escribe a mano dentro de prueba.cs: primero como un bloque de comentario /* … */ para pensarla, y luego la copias a una cadena de C# en el método. Así queda el archivo:

prueba.cs
├─ // encabezado: esquema + hueco para firmas   ← Paso 1.3 y 2.2
├─ /* ---- La consulta ----  ... ---- fin ---- */ ← Paso 3   (SQL a mano)
├─ using System.Data; using Dapper; ...          ← Paso 4.1
├─ public class OrderReportService(...) { ... }   ← Paso 4   (método de servicio)
├─ [ApiController] public class OrderReportController(...) { ... }  ← Paso 5  (endpoint)
└─ public record OrderLineRow(...);  public record PagedResult<T>(...);  ← al final (si no te dan los tipos)

Paso 2 · Las firmas por el chat clave

Las 3 piezas que siguen son un esqueleto invariante. Lo único que cambia con sus firmas son nombres y formas. Ese ajuste es la habilidad.

2.1 · Cómo te las dan

  • Pegan código C# en el chat de la reunión (Teams / Meet / Zoom) o en un bloc compartido. A veces un screenshot.
  • Todo de golpe al inicio, o a cuentagotas: primero el DTO, y cuando llegas al endpoint te dan el objeto de query.
  • Típicamente: el DTO / tipo de fila, el tipo de resultado (PagedResult<T> o como lo llamen), a veces un objeto de query y/o una interfaz de servicio.

Truco: no memorices "qué te pueden dar" como una lista — memoriza que siempre son las mismas 4 piezas (DTO, resultado, query, interfaz) y que cada una tiene un hueco fijo esperándola (tabla 2.4). Reconocer la pieza es más fácil que recordar la lista.

2.2 · Qué haces en cuanto las tengas (30 s, en voz alta)

  1. Pega el bloque tal cual en el hueco del encabezado de prueba.cs. Que se vea en pantalla — es tu contrato.
  2. Read-back: "Ok, entonces mi método es GetOrderLinesAsync, devuelve Task<PagedResult<OrderLineDto>> y recibe ReportQuery. OrderLineDto tiene OrderLineId, OrderId, OrderDate, ProductName, LineTotal. ¿Lo leo bien?" — gana tiempo y demuestra que lo miraste.
  3. Pregunta lo ambiguo antes de teclear (tabla siguiente).

Truco: practica el read-back como una fórmula fija: "Ok, entonces mi método es X, devuelve Y, recibe Z." Memoriza la forma de la frase, no un ejemplo — así te sirve sin importar qué firma te den.

2.3 · Preguntas de aclaración, por tipo de firma

Te dan…Preguntas
Un DTO / fila¿LineTotal/Total/Amount lo calculo yo (Quantity*UnitPrice) o es columna? Si trae un campo que mi base no tiene (p. ej. CustomerName) → "añado un JOIN a esa tabla". Si no trae OrderLineId"¿esto es agregado por pedido, no por línea?"
Un objeto de query¿PageIndex/Page es base 0 o base 1? ¿ToExclusive es exclusivo de verdad? ¿PageSize tiene tope o lo pongo yo?
Un tipo de resultado¿Total/Count es el total filtrado o el de la tabla? ¿record posicional o propiedades? ¿lleva PageIndex/PageSize o solo Items/Total?
Una interfaz de servicio¿La implemento en una clase concreta? ¿Cómo llega la conexión — IDbConnection inyectado, connection string? (asumo constructor y lo digo)

Truco: no memorices las preguntas — memoriza una sola pregunta madre por firma: "¿qué pasa si esto no cuadra con mi base?" Cada fila de la tabla es esa misma pregunta aplicada a una pieza distinta.

2.4 · El mapeo — cada firma cae en un hueco fijo

FirmaDónde encaja
Campos del DTOLos alias del SELECT — Dapper mapea por nombre, cada alias debe coincidir exacto. Y el ReadAsync<SuDto>().
Objeto de queryLos parámetros del método (ReportQuery q en vez de 4 sueltos) y el binding del endpoint ([FromQuery] ReportQuery q). Dentro usas q.From
Tipo de resultadoEl return new SuTipo(...) y el ActionResult<SuTipo> del endpoint.
Interfaz de serviciopublic class OrderReportService : ISuInterfaz + la firma del método copiada literal.
Nombre del métodoEse, tal cual. No lo "mejores".

Truco: esta tabla es la más importante para memorizar de toda la prueba, porque es la que te salva si te dan firmas distintas a las que practicaste. Apréndela por columna derecha: "DTO → alias del SELECT", "query → parámetros", "resultado → el return", "interfaz → el : I...".

2.5 · Ejemplo de adaptación

Te pegan esto en el chat:

public record ReportQuery(DateTime From, DateTime ToExclusive, int Page, int Size);
public record OrderSummaryDto(int OrderId, DateTime OrderDate, string CustomerName, decimal Total);
public record Paged<T>(IReadOnlyList<T> Rows, long Count);
public interface IOrderReport { Task<Paged<OrderSummaryDto>> RunAsync(ReportQuery q); }

Lo que dices y haces — comparas con tu base:

  • "El DTO no trae OrderLineId, trae un Total por pedido → es agregado por pedido. Agrupo y sumo."
  • "Trae CustomerName, que mi base no tenía → añado JOIN a Customer. Y como no muestro producto, quito el JOIN a Product." Mismo patrón, distinto set de tablas.
  • "Count es long → leo el COUNT(*) como long: ReadSingleAsync<long>()."
  • Método: public async Task<Paged<OrderSummaryDto>> RunAsync(ReportQuery q), usas q.From, q.Size… Return: new Paged<OrderSummaryDto>(rows, count). Endpoint: [FromQuery] ReportQuery q.

Truco: practica este ejemplo con una firma distinta cada vez (cambia un campo, agrega uno, quita un JOIN) — si solo repites este mismo ejemplo, memorizas el resultado, no la habilidad de adaptar. Fuerza la variación.

Si NO te dan firmas y dicen "estructúralo como lo harías": usas las tuyas (Paso 3–5) y explicas cada decisión. Prepárate para ambos.

Paso 3 · La consulta SQL pieza 1 — lo que decide

La escribes como SQL puro primero, en un bloque comentado, para pensarla sin pelear con el C#. Luego la mueves al método. La construyes por trozos, narrando.

3.1 · Abre el bloque y el SELECT

Escribes (debajo del encabezado, en prueba.cs):

/* ---- La consulta ----
SELECT
    ol.OrderLineId,
    o.OrderId,
    o.OrderDate,
    p.Name                       AS ProductName,
    ol.Quantity,
    ol.UnitPrice,
    ol.Quantity * ol.UnitPrice   AS LineTotal

Dices: "Empiezo por lo que devuelve el reporte: id de línea, datos del pedido, nombre del producto, cantidad y precio. El total de línea no lo guardo, lo calculo — Quantity * UnitPrice."

Por qué: arrancar por el SELECT te obliga a decidir la forma de la fila antes que nada. Alias legibles (ProductName, LineTotal) para que el DTO mapee por nombre. Total calculado = no duplicas un dato derivable.

Truco: frase 1 de las 6 (pestaña Memoriza esto): "traigo columnas con alias claros; el total lo calculo, no lo guardo." Si recuerdas por qué no guardas LineTotal como columna —se desincroniza si cambia el precio—, la línea de código te sale sola.

3.2 · FROM + los 2 JOIN

Escribes:

FROM dbo.OrderLine AS ol
INNER JOIN dbo.[Order]  AS o ON o.OrderId   = ol.OrderId
INNER JOIN dbo.Product  AS p ON p.ProductId = ol.ProductId

Dices: "Arranco por OrderLine, que es el grano del reporte — una fila por línea. Le pego el pedido por OrderId y el producto por ProductId. INNER porque solo quiero líneas completas; si quisiera incluir líneas sin producto sería LEFT. Alias cortos y prefijo todas las columnas."

Por qué: empezar por la tabla más fina y colgar las demás es el patrón estándar de un reporte tipo estrella. [Order] entre corchetes porque Order es palabra reservada. Prefijar columnas evita ambigüedad.

Truco: "el grano manda" — la tabla con la que arrancas siempre es la que tiene una fila por cada resultado que quieres mostrar. Si el reporte fuera por pedido (no por línea), arrancarías en [Order]. Esa sola idea reemplaza memorizar "siempre empiezo en OrderLine".

3.3 · WHERE — el filtro de fecha semiabierto

Escribes:

WHERE o.OrderDate >= @fromDate
  AND o.OrderDate <  @toExclusive

Dices: "Rango semiabierto: mayor o igual que el inicio, y estrictamente menor que el fin. No uso BETWEEN porque incluye el extremo, y con datetime eso te mete o te deja fuera el último día según la hora. El cliente manda como toExclusive el primer instante del día siguiente. Y filtro contra OrderDate tal cual, sin CAST ni CONVERT, para que el predicado sea SARGable y pueda usar un índice."

Por qué: es EL detalle que separa a alguien que ha hecho reportes de fechas de alguien que no. BETWEEN + datetime = bug clásico de "me falta o me sobra el último día". CAST(OrderDate AS date) = el motor no puede usar el índice → scan.

Truco: "≥ abre, < cierra, nunca ENTRE." Si memorizas solo una línea de toda la prueba, que sea esta — es la que ellos citan como "la pregunta que decide" en sus propias notas.

3.4 · ORDER BY — determinista

Escribes:

ORDER BY o.OrderDate DESC, ol.OrderLineId DESC

Dices: "Ordeno por fecha descendente, que es lo que le importa al usuario, y desempato con OrderLineId, que es único. OFFSET/FETCH necesita un orden total: si dos filas tienen la misma fecha y no hay desempate, el motor las puede devolver en cualquier orden y al paginar se repiten o se saltan filas."

Por qué: paginación sin orden determinista = filas duplicadas o perdidas entre páginas. La columna final tiene que ser única (la PK sirve). Es sutil y es justo lo que quieren oírte razonar.

Truco: "sin desempate único, no hay orden real." Pregúntate siempre: "¿mi última columna del ORDER BY es una PK o algo único?" Si la respuesta es no, falta una columna — no importa si es esta prueba u otra distinta el martes.

3.5 · OFFSET / FETCH — la paginación

Escribes:

OFFSET (@pageIndex * @pageSize) ROWS FETCH NEXT @pageSize ROWS ONLY;

Dices: "Paginación en el servidor: salto pageIndex * pageSize filas y traigo las siguientes pageSize. Para un reporte donde saltas a la página N, OFFSET está bien. Si fuera scroll infinito usaría keyset — filtrar por la última fila vista — que no se degrada al avanzar."

Por qué: OFFSET es simple y correcto aquí; mencionar keyset muestra que sabes su límite (en la página 10.000, OFFSET aún lee y descarta 100.000 filas) sin complicar el ejercicio.

Truco: la fórmula completa cabe en 4 palabras: "salta, trae; o sigue." OFFSET = salta N, trae M. Keyset = "sigue después de la última que viste." Nombrar el segundo sin implementarlo ya es la señal senior — no necesitas escribirlo.

3.6 · El segundo SELECT — el total

Escribes (y cierras el bloque):

SELECT COUNT(*)
FROM dbo.OrderLine AS ol
INNER JOIN dbo.[Order] AS o ON o.OrderId = ol.OrderId
WHERE o.OrderDate >= @fromDate
  AND o.OrderDate <  @toExclusive;
---- fin ---- */

Dices: "Y el total para el paginador, con el mismo WHERE pero sin el JOIN a producto ni el ORDER BY, que no hacen falta para contar. Los dos SELECT los mando juntos, un solo viaje a la base."

Por qué: el paginador necesita el total de filas que cumplen el filtro, no el de la tabla. Contar no necesita ordenar ni el join a Product.

Truco: "mismo filtro, menos peso." El COUNT copia solo el WHERE de arriba — ni el JOIN a Product, ni el ORDER BY. Si te preguntas "¿qué llevo y qué quito?", la respuesta siempre es esa.

Si preguntan por índices: "Un índice en [Order](OrderDate) para el filtro y el orden; y las FK de OrderLine (OrderId, ProductId) indexadas para el JOIN. Con eso no hay scan."

Paso 4 · El método de servicio pieza 2

4.1 · La clase y la firma

Escribes (debajo del bloque SQL; usa la firma que te dieron, o esta):

using System.Data;
using Dapper;
using Microsoft.Data.SqlClient;

public class OrderReportService(string connectionString)
{
    public async Task<PagedResult<OrderLineRow>> GetOrderLinesAsync(
        DateTime fromDate, DateTime toExclusive, int pageIndex, int pageSize)
    {
    }
}

Dices: "El servicio recibe el connection string por constructor — en real vendría de configuración e inyección de dependencias. El método es async porque va a la base."

Por qué: si te dieron una interfaz, aquí pones : IEsaInterfaz y copias la firma literal. El primary constructor (string connectionString) es C# moderno, ahorra ceremonia.

Truco: el patrón "clase + constructor primario + método async" es idéntico en cualquier servicio que hayas escrito en SEUS. No es sintaxis nueva que memorizar — es la misma forma que ya usas, con otro nombre.

4.2 · Mueve el SQL a un const

Escribes (dentro del método) — copias la consulta del bloque de arriba a una cadena:

const string sql = @"
    SELECT ol.OrderLineId, o.OrderId, o.OrderDate,
           p.Name AS ProductName, ol.Quantity, ol.UnitPrice,
           ol.Quantity * ol.UnitPrice AS LineTotal
    FROM dbo.OrderLine AS ol
    INNER JOIN dbo.[Order] AS o ON o.OrderId   = ol.OrderId
    INNER JOIN dbo.Product AS p ON p.ProductId = ol.ProductId
    WHERE o.OrderDate >= @fromDate AND o.OrderDate < @toExclusive
    ORDER BY o.OrderDate DESC, ol.OrderLineId DESC
    OFFSET (@pageIndex * @pageSize) ROWS FETCH NEXT @pageSize ROWS ONLY;

    SELECT COUNT(*)
    FROM dbo.OrderLine AS ol
    INNER JOIN dbo.[Order] AS o ON o.OrderId = ol.OrderId
    WHERE o.OrderDate >= @fromDate AND o.OrderDate < @toExclusive;";

Dices: "Muevo la consulta a una cadena literal — el @ me deja escribirla en varias líneas. Es la misma de antes."

Por qué: pensaste la SQL como SQL; ahora es un dato del método. const porque no cambia.

Truco: es un copy-paste literal del bloque del Paso 3, no una reescritura — si Paso 3 ya salió de memoria, este paso no añade nada nuevo que aprender, solo mover.

4.3 · Conexión + QueryMultiple

Escribes:

using var conn = new SqlConnection(connectionString);
using var grid = await conn.QueryMultipleAsync(sql,
    new { fromDate, toExclusive, pageIndex, pageSize });

Dices: "Abro la conexión con using para que se cierre sola. Los parámetros van como objeto anónimo — Dapper los convierte en parámetros de SqlCommand, nunca los concateno en el texto: eso es lo que mata la inyección SQL. Y como son dos SELECT, QueryMultiple: un viaje, leo los dos resultados."

Por qué: parametrizar = seguridad + plan de ejecución reutilizable. using = no fugas de conexión. QueryMultiple = lista + count en un round-trip.

Truco: tres palabras, tres líneas: using (se cierra sola) → objeto anónimo (nunca texto concatenado) → Multiple (porque son 2 SELECT). Si dices las tres palabras en voz alta, el código sale detrás.

4.4 · Leer y devolver

Escribes:

    var items = (await grid.ReadAsync<OrderLineRow>()).ToList();
    var total = await grid.ReadSingleAsync<int>();

    return new PagedResult<OrderLineRow>(items, total, pageIndex, pageSize);

Dices: "Leo la primera consulta como la lista de filas, la segunda como un entero — el total. Y devuelvo el resultado paginado con lo que el cliente necesita para pintar el paginador."

Por qué: el orden de los Read sigue el orden de los SELECT. Si el tipo de resultado que te dieron se llama distinto o tiene otros campos, aquí lo ajustas (Paso 2).

Truco: "leo en el mismo orden en que escribí." Los dos Read son un espejo exacto de los dos SELECT de arriba — si te pierdes, cuenta cuántos SELECT escribiste y sabrás cuántos Read te faltan.

Paso 5 · El endpoint pieza 3

5.1 · El controller y la acción

Escribes (debajo del servicio):

using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/reports")]
public class OrderReportController(OrderReportService service) : ControllerBase
{
    [HttpGet("order-lines")]
    public async Task<ActionResult<PagedResult<OrderLineRow>>> GetOrderLines(
        [FromQuery] DateTime fromDate,
        [FromQuery] DateTime toExclusive,
        [FromQuery] int pageIndex = 0,
        [FromQuery] int pageSize = 20)
    {
    }
}

Dices: "GET porque es una lectura. Los filtros van en la query string: fromDate, toExclusive, y la paginación con valores por defecto. El servicio entra por constructor."

Por qué: [ApiController] te da el binding y las respuestas de error automáticas. ActionResult<T> te deja devolver el dato o un código de error.

Truco: es el mismo esqueleto [ApiController] + constructor + [HttpGet] que ya escribes en SEUS cada vez que expones un endpoint. No hay nada aquí que no hayas tecleado cien veces con otro nombre de recurso.

5.2 · Validar y sanear

Escribes (dentro de la acción):

if (toExclusive <= fromDate)
    return BadRequest("El rango de fechas no es valido.");

pageIndex = Math.Max(0, pageIndex);
pageSize  = Math.Clamp(pageSize, 1, 100);

Dices: "Valido lo que el servicio no puede saber: que el rango tenga sentido → 400. Y saneo la paginación aquí, en el borde: pageIndex no negativo, pageSize con tope de 100, para que nadie pida un millón de filas de golpe."

Por qué: el controller es la frontera con el mundo — es donde se valida la entrada. El tope de página protege la base.

Truco: "la frontera valida, el servicio confía." Todo lo que entra de fuera (fechas, números de página) se limpia aquí, antes de llegar al servicio — así nunca tienes que recordar validar dos veces en dos capas.

5.3 · Delegar y devolver

Escribes:

var result = await service.GetOrderLinesAsync(fromDate, toExclusive, pageIndex, pageSize);
return Ok(result);

Dices: "Llamo al servicio y devuelvo 200 con el resultado. Si no hay filas en ese rango, sigue siendo 200 con lista vacía — no 404: el reporte existe, simplemente no tiene datos ese mes. El controller queda fino: valida, delega, devuelve. Nada de SQL aquí."

Por qué: separación de capas. 404 es "el recurso no existe", no "la consulta no trajo nada".

Truco: regla de una línea para todos los códigos HTTP de la prueba: "el recurso existe pero está vacío = 200; el recurso no existe = 404; tú mandaste algo mal = 400." Con esa frase decides cualquier código sin memorizar una tabla.

Paso 6 · Repaso en voz alta 30 s

Antes de decir "listo", recorre el código señalando en pantalla y recita:

Truco: practica este repaso señalando en pantalla con el cursor cada cosa que nombras, incluso solo en tus ensayos. El gesto físico ancla la lista mejor que decirla mirando al techo.

Paso 7 · La transacción — te van a preguntar la otra mitad

  • "Esto es un SELECT: no necesita transacción explícita. Corre bajo READ COMMITTED por defecto y me vale."
  • "Un matiz: mando dos SELECT (lista y COUNT). Entre uno y otro podría colarse un INSERT y que el total no cuadre con la página. Si esa consistencia importa, envuelvo los dos en una transacción con aislamiento SNAPSHOT —lectura consistente sin bloquear escritores— o REPEATABLE READ."
  • "Donde una transacción es obligatoria: una escritura que toca varias tablas. Crear un pedido = insertar Order + sus OrderLine + descontar Stock. O las tres o ninguna."
  • "Con Dapper: conn.BeginTransaction(), se lo paso a cada Execute, Commit() al final, Rollback() en el catch. El using de la transacción hace rollback si salgo por excepción sin llegar al commit."
  • "Subiría a SERIALIZABLE solo si necesito que nadie inserte en el rango que estoy leyendo mientras decido —por ejemplo, validar que no haya solapamiento antes de insertar."

Truco: una sola pregunta desata las 5 frases: "¿esto lee o escribe en más de una tabla?" Lee = no necesita transacción. Escribe en varias = la necesita, y ahí es donde entra BeginTransaction/Commit/Rollback.

Paso 8 · Angular (hablado)

Casi seguro te preguntan (por el JD y las notas):

  1. Observable vs Promesa — la clásica de arranque.
  2. Native Federation — qué te aportó y qué te costó. Tu fuerte, lúcelo, ten un ejemplo concreto.
  3. El último test de componente que escribiste y qué comprobaba (Karma/Jasmine). Ten uno real preparado.
  4. Signals + change detection — SEUS es signals-first, es terreno tuyo.
  5. AWS Lambda → Azure — qué cambia. Punto a cuidar: di lo que NO equivale.
  6. Tu inglés — te lo van a testear sin rodeos.

Truco: no memorices 17 respuestas sueltas de la tabla de abajo — memoriza la fórmula de 3 partes que las genera todas: núcleo técnico + término exacto + tu caso real en SEUS. Aplicada a cualquier pregunta nueva que no esté aquí, sigue funcionando.

Cómo respondes (aplica las 3 reglas de arriba): (1) directo, sin "creo/me parece"; (2) el núcleo técnico con el término exacto; (3) lo aterrizas en SEUS con un caso concreto — la tabla "Anclado a tu experiencia" te lo da hecho. Núcleo + término + ejemplo real = respuesta que convence.

PreguntaNúcleo de la respuesta
Observable vs PromesaPromesa: un valor, ansiosa (arranca sola), no se cancela. Observable: 0..n valores en el tiempo, perezoso (no pasa nada sin subscribe), cancelable, y con operadores. HTTP one-shot: casi da igual (HttpClient ya devuelve Observable). Búsquedas, streams, cancelación: Observable gana.
RxJS que usasswitchMap (cancela la anterior — typeahead), debounceTime+distinctUntilChanged, catchError, combineLatest, shareReplay (cachear), takeUntilDestroyed (limpiar). mergeMap vs concatMap vs switchMap: paralelo / en orden / solo la última.
Fuga de memoria / unsubscribeUn subscribe sin cerrar sigue vivo tras destruir el componente. Se evita con async pipe (Angular gestiona), takeUntilDestroyed(), o takeUntil(destroy$) en ngOnDestroy.
Native Federation: aportó / costóAportó: deploys independientes por módulo, equipos que no se pisan, no rebuild del monolito. Costó: alinear versiones de Angular y shared deps (mal configurado revienta en runtime), debug entre host y remoto, primer setup del federation.config. Ten un caso concreto: "un módulo que actualizamos sin tocar los otros 17".
Change detectionZone.js parchea lo async y marca el árbol como dirty; Angular re-evalúa. OnPush: solo si cambia una @Input por referencia, un evento del template, o un observable con async. Signals (v16+): CD granular, solo lo que lee esa señal. Angular va a zoneless.
Signalssignal() valor reactivo, computed() derivado que se recalcula solo, effect() efecto secundario. Vs RxJS: signals para estado síncrono en la vista; RxJS para eventos async y streams. input()/output() como signals desde v17.
Lifecycleconstructor: solo DI, nada de lógica. ngOnInit: arranque (llamadas, subscribe). ngOnChanges: reacciona a cambios de @Input. ngOnDestroy: limpiar. ngAfterViewInit: ya hay @ViewChild.
Servicios y DI@Injectable({ providedIn: 'root' }) = singleton tree-shakeable. Inyector jerárquico: un provider en un componente da una instancia por ese subárbol. inject() function-style en vez de constructor.
RoutingLazy con loadComponent/loadChildren. Guards (CanActivate) para auth. Resolvers para precargar datos. ActivatedRoute para params. Se declara con provideRouter(routes).
HTTP interceptorsFunción que envuelve cada request: meter el token, logging, reintentos, manejo global de errores (401 → refresh o login). Se registran en provideHttpClient(withInterceptors([...])).
StandaloneSin NgModule: cada componente declara sus imports. bootstrapApplication + provideHttpClient(), provideRouter(). Mejor tree-shaking, lazy por componente.
EstadoPara local: servicio con signal o BehaviorSubject + asObservable(). Para app grande y compleja: NgRx (store, actions, effects, selectors) — pero solo si el estado compartido lo justifica; si no, sobra.
PerformanceOnPush, trackBy/@for track en listas, lazy loading, @defer para diferir bloques pesados, NgOptimizedImage, evitar funciones en el template.
Testing (Karma/Jasmine · Jest)Jasmine = sintaxis (describe/it/expect, spies), Karma = corre en navegador real. Jest = Node + jsdom, más rápido, snapshots. Comunes: TestBed, jasmine.createSpyObj/jest.fn(), HttpTestingController (request sin red), ComponentFixture + fixture.detectChanges(), fakeAsync/tick. En SEUS: Karma/Jasmine (ver anclaje abajo).
El último test que escribisteTen uno real y concreto — qué componente/servicio y qué comprobaba (ver anclaje abajo con un ejemplo del repo). Concreto > genérico.
FormulariosReactivos y tipados (FormGroup<{...}>), validadores sync y async, que reflejan las reglas del back, valueChanges como Observable.
AWS Lambda → AzureEquivale a Azure Functions (serverless, event-driven). No equivale 1:1: IAM ≠ Entra ID / Managed Identity; API Gateway ≠ API Management; DynamoDB ≠ Cosmos DB; S3 ≠ Blob Storage; CloudWatch ≠ App Insights. "He usado Lambda; en Azure me pondría al día con Functions y App Insights, el modelo mental es el mismo".

Anclado a tu experiencia real en SEUS — así respondes con un caso concreto, no con teoría. (Todo esto es tu repo MicroFrontsSeusWeb: backoffice/POS farmacéutico, más de una docena de microfrontends con Native Federation, Angular 20.3, Reactive Forms en todo + signals en el código nuevo, mayor contribuidor con 86 commits.) Rellena los […] con tu detalle antes de la entrevista.

TemaCómo lo aterrizas (real)
Arquitectura frontend"SEUS son más de una docena de microfrontends con @angular-architects/native-federation, Angular 20.3, standalone. Un host/shell y cada módulo —compra directa, recepción electrónica, monitor de embarques, consultas de inventario…— es un remoto con su remoteEntry.json, mapeado en federation.manifest.json. Más una librería común: componentes y servicios que todos consumen."
Native Federation — aportó"Cada módulo, su repo y su release. [Actualicé el módulo de X sin tocar los otros]. La librería compartida evita duplicar el buscador de producto, el manejo de errores, el spinner."
Native Federation — costó"Alinear la versión de Angular y las shared deps en todos los remotos — si uno carga otra versión de @angular/core o de la librería, revienta en runtime. El manifest.json apuntando a local vs prod. Debuggear entre el host y un remoto: saber qué versión cargó."
Estado — signals"Buena parte de los módulos —compra directa, consulta de facturas, devoluciones a proveedor, inventarios— ya usan signals: signal<T>() para el estado del componente, computed() para lo derivado. En confirmación de lote, puedeAgregarLote = computed(() => …) recalcula solo cuando cambian sus dependencias. inject() en vez de constructor, input()/output() como signals. Sin NgRx — el estado compartido es mínimo y vive en servicios de la librería."
Observable vs Promesa"En SEUS convivimos: el acceso a datos va envuelto en promesas (capa .promise.ts) para el request→response simple —un await se lee mejor que un subscribe para un guardar—, y Observable donde hay stream. La diferencia: promesa = un valor, ansiosa, no cancelable; Observable = 0..n, perezoso, cancelable, con operadores. Si el equipo prefiere todo-Observable, es un ajuste chico."
RxJS concreto"El buscador de producto de la librería: un Subject con debounceTime(500) + takeUntil(destroy$), a partir de 3 caracteres. La paginación de resultados es por cursor — la API devuelve un Hash, y al hacer scroll al final del autocomplete pido la siguiente página con ese hash. Es keyset, no OFFSET. En los módulos también uso switchMap [para X — cancelar una carga al cambiar de filtro] y combineLatest [para combinar N filtros en una sola consulta]."
Change detection"En SEUS uso OnPush en los componentes con listas o inputs pesados —solo re-renderiza si cambia una @Input por referencia o un evento del template—, y en el código nuevo, signals + computed para reactividad granular: solo se re-evalúa lo que lee esa señal. Angular 20, la dirección es zoneless."
Testing"En SEUS es Karma/Jasmine (ng test, cobertura con karma-coverage). Patrón: TestBed, jasmine.createSpyObj para las dependencias, fixture.detectChanges()." [Jest: di la verdad de cuánto lo has tocado — en SEUS no está. Si es poco: "lo conozco, el salto desde Karma es menor"]
El último test que escribiste[RELLENA con uno tuyo. Ejemplo real del repo si te sirve: el spec de MasterGuardarPedidoService (guardar pedido manual) — que si la validación falla (!procesoCorrecto) muestra el spinner, registra el log, llama al manejador de errores y devuelve el resultado; con spies de PedidoManualPromise, LoadingService, ErrorHandlerPromiseService.]
Arquitectura de un módulo"Cada feature son 3 capas: assemblyRequest arma el request, .promise hace la llamada HTTP envuelta, .service orquesta (loading, errores, log). El componente solo pinta y llama al service."
Formularios"Reactive Forms en casi todos los módulos: FormBuilder, FormGroup, y todo el set de Validatorsrequired, pattern, minLength, email, y validadores async con composeAsync—. Los formularios de alta [de X] reflejan las mismas reglas que el backend .NET, mismos límites en los dos lados. En el código nuevo combino con computed() para la validación de conjunto (puedeAgregarLote)."
Routing / lazy"En el host cada módulo es una ruta lazy que carga su remoto; el federation.manifest.json mapea nombre → remoteEntry.json, así apunto a local o a prod sin recompilar el shell."

Truco: esta tabla no se memoriza — se vive. Cada fila ya pasó por tus manos en SEUS; en vez de repetirla en abstracto, repásala mirando tu propio repo un minuto antes de dormir cada noche de esta semana.

Paso 9 · Cierre — tus preguntas

Cuando te pregunten "¿tienes dudas?", ten 2–3 listas. Muestran interés real:

Truco: elige tus 2 favoritas de esta lista antes del martes y practícalas en voz alta — no llegues a improvisar la pregunta en el momento, es lo único de todo el guion que sí puedes decir casi textual porque nadie evalúa tu SQL en esta parte.

Si te trabas recuperación

Truco: esta lista no se memoriza como texto — se ensaya provocándola a propósito. En tu Corrida 3 (grabándote la voz), mete adrede un error a mitad y practica recuperarte en voz alta. Es la única forma de que estos reflejos existan cuando de verdad tiembles.

Si el problema es otro preparado para todo

El esqueleto (Paso 3–5) no cambia — solo una cláusula o un método. Aquí, cada variante con el código y en qué parte del fichero va.

A · Filtran por estado (o cliente, o categoría) en vez de fecha

En el SQL — cambias solo el WHERE:

WHERE o.Status = @status

En el método — el parámetro y el objeto anónimo:

public async Task<PagedResult<OrderLineRow>> GetOrderLinesAsync(
    string status, int pageIndex, int pageSize)
{
    // ... mismo cuerpo ...
    new { status, pageIndex, pageSize }   // en QueryMultipleAsync
}

En el endpoint[FromQuery] string status en vez de las fechas. Todo lo demás igual.

"Mismo patrón, distinto predicado. Status es una igualdad — = @status, no LIKE."

B · Totales agregados por pedido (no una fila por línea)

En el SQL — cambia el SELECT, entra GROUP BY, y el COUNT cuenta pedidos distintos:

SELECT
    o.OrderId,
    o.OrderDate,
    COUNT(*)                        AS LineCount,
    SUM(ol.Quantity * ol.UnitPrice) AS OrderTotal
FROM dbo.OrderLine AS ol
INNER JOIN dbo.[Order] AS o ON o.OrderId = ol.OrderId
WHERE o.OrderDate >= @fromDate AND o.OrderDate < @toExclusive
GROUP BY o.OrderId, o.OrderDate
ORDER BY o.OrderDate DESC, o.OrderId DESC
OFFSET (@pageIndex * @pageSize) ROWS FETCH NEXT @pageSize ROWS ONLY;

SELECT COUNT(DISTINCT o.OrderId)
FROM dbo.OrderLine AS ol
INNER JOIN dbo.[Order] AS o ON o.OrderId = ol.OrderId
WHERE o.OrderDate >= @fromDate AND o.OrderDate < @toExclusive;

El tipo de fila cambia a OrderSummaryRow(int OrderId, DateTime OrderDate, int LineCount, decimal OrderTotal). Método y endpoint: igual, solo el genérico.

"Ahora cada fila es un pedido, no una línea. GROUP BY por lo que no está agregado. El total del paginador es COUNT(DISTINCT o.OrderId) — si no, contaría líneas."

C · Búsqueda por nombre o SKU

En el SQL — al WHERE:

  AND (p.Name LIKE @term + '%' OR p.Sku LIKE @term + '%')

Método/endpoint — parámetro string term.

"Prefijo (@term + '%') puede usar índice. '%' + @term + '%' —contiene— ya no; si necesitan eso, full-text o acepto el scan, y lo digo."

D · Un solo registro por id (no una lista)

SQL — sin paginación ni ORDER BY:

SELECT ol.OrderLineId, o.OrderId, o.OrderDate, p.Name AS ProductName,
       ol.Quantity, ol.UnitPrice, ol.Quantity * ol.UnitPrice AS LineTotal
FROM dbo.OrderLine AS ol
INNER JOIN dbo.[Order]  AS o ON o.OrderId   = ol.OrderId
INNER JOIN dbo.Product  AS p ON p.ProductId = ol.ProductId
WHERE ol.OrderLineId = @id;

Método:

public async Task<OrderLineRow?> GetByIdAsync(int id)
{
    using var conn = new SqlConnection(connectionString);
    return await conn.QuerySingleOrDefaultAsync<OrderLineRow>(sql, new { id });
}

Endpoint:

[HttpGet("order-lines/{id:int}")]
public async Task<ActionResult<OrderLineRow>> GetById(int id)
    => await service.GetByIdAsync(id) is { } row ? Ok(row) : NotFound();

"QuerySingleOrDefault devuelve null si no existe → 404. Single (sin OrDefault) reventaría; First ocultaría duplicados."

E · Un INSERT (crear un pedido)

En el servicio — método nuevo:

public async Task<int> CreateOrderAsync(NewOrder input)
{
    const string sql = @"
        INSERT INTO dbo.[Order] (OrderDate, Status)
        VALUES (@OrderDate, @Status);
        SELECT CAST(SCOPE_IDENTITY() AS int);";

    using var conn = new SqlConnection(connectionString);
    return await conn.ExecuteScalarAsync<int>(sql, input);
}

En el endpoint:

[HttpPost("orders")]
public async Task<ActionResult<int>> Create(NewOrder input)
{
    var id = await service.CreateOrderAsync(input);
    return CreatedAtAction(nameof(GetById), new { id }, id);
}

"ExecuteScalarAsync<int> + SCOPE_IDENTITY() para devolver el id nuevo. 201 con Location apuntando al recurso creado. El 400 por body inválido lo da el binder si el DTO trae DataAnnotations."

F · Un INSERT en varias tablas → transacción

En el servicio:

public async Task<int> CreateOrderWithLinesAsync(NewOrder order, IEnumerable<NewLine> lines)
{
    using var conn = new SqlConnection(connectionString);
    await conn.OpenAsync();
    using var tx = conn.BeginTransaction();
    try
    {
        var orderId = await conn.ExecuteScalarAsync<int>(
            @"INSERT INTO dbo.[Order] (OrderDate, Status) VALUES (@OrderDate, @Status);
              SELECT CAST(SCOPE_IDENTITY() AS int);",
            order, tx);

        foreach (var l in lines)
            await conn.ExecuteAsync(
                @"INSERT INTO dbo.OrderLine (OrderId, ProductId, Quantity, UnitPrice)
                  VALUES (@orderId, @ProductId, @Quantity, @UnitPrice);",
                new { orderId, l.ProductId, l.Quantity, l.UnitPrice }, tx);

        tx.Commit();
        return orderId;
    }
    catch
    {
        tx.Rollback();
        throw;
    }
}

"Abro la conexión explícitamente con OpenAsync porque necesito la transacción. Se la paso a cada Execute. Commit al final; si algo truena, Rollback y relanzo. O todo o nada."

G · Scroll infinito → keyset en vez de OFFSET

En el SQL — el cliente manda la última fila que vio, no un número de página:

WHERE o.OrderDate >= @fromDate AND o.OrderDate < @toExclusive
  AND (@lastDate IS NULL
       OR o.OrderDate < @lastDate
       OR (o.OrderDate = @lastDate AND ol.OrderLineId < @lastId))
ORDER BY o.OrderDate DESC, ol.OrderLineId DESC
OFFSET 0 ROWS FETCH NEXT @pageSize ROWS ONLY;

Método/endpoint — en vez de pageIndex, los parámetros DateTime? lastDate, int? lastId.

"Sin OFFSET que crezca: la primera página y la 10.000 cuestan lo mismo, porque el WHERE salta directo con el índice. El @lastDate IS NULL es la primera llamada."

H · Sin Dapper — ADO.NET puro
using var conn = new SqlConnection(connectionString);
await conn.OpenAsync();
using var cmd = new SqlCommand(sql, conn);
cmd.Parameters.AddWithValue("@fromDate", fromDate);
cmd.Parameters.AddWithValue("@toExclusive", toExclusive);
cmd.Parameters.AddWithValue("@pageIndex", pageIndex);
cmd.Parameters.AddWithValue("@pageSize", pageSize);

var items = new List<OrderLineRow>();
using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
    items.Add(new OrderLineRow(
        reader.GetInt32(0), reader.GetInt32(1), reader.GetDateTime(2),
        reader.GetString(3), reader.GetInt32(4),
        reader.GetDecimal(5), reader.GetDecimal(6)));

await reader.NextResultAsync();
await reader.ReadAsync();
var total = reader.GetInt32(0);

"Lo mismo, más verboso: parámetros con cmd.Parameters, Read() en bucle, mapeo por índice de columna. NextResult para pasar al segundo SELECT. Dapper me ahorra exactamente este mapeo."

El archivo completo tu objetivo

Así queda prueba.cs al final. Practica tecleándolo entero, narrando, hasta que salga en ~12–15 min sin trabarte.

Truco final: no lo memorices como un bloque — ya lo memorizaste en 5 piezas (sección "1 · El archivo" de Memoriza esto). Este código es solo la prueba de que las piezas encajan. Si te sale completo, ya sabes el guion; si se traba en una costura, vuelve a esa pieza sola, no a repasar todo desde cero.

// Prueba tecnica - reporte de lineas de pedido por rango de fechas, paginado
//
// Mis 3 tablas:
//   [Order]   (OrderId PK, OrderDate, Status)
//   Product   (ProductId PK, Sku, Name)
//   OrderLine (OrderLineId PK, OrderId -> [Order], ProductId -> Product, Quantity, UnitPrice)
//
// Firmas del chat:  (aqui va lo que me peguen)

using System.Data;
using Dapper;
using Microsoft.Data.SqlClient;
using Microsoft.AspNetCore.Mvc;

// ---------- 1) El metodo de servicio ----------
public class OrderReportService(string connectionString)
{
    public async Task<PagedResult<OrderLineRow>> GetOrderLinesAsync(
        DateTime fromDate, DateTime toExclusive, int pageIndex, int pageSize)
    {
        const string sql = @"
            SELECT ol.OrderLineId, o.OrderId, o.OrderDate,
                   p.Name AS ProductName, ol.Quantity, ol.UnitPrice,
                   ol.Quantity * ol.UnitPrice AS LineTotal
            FROM dbo.OrderLine AS ol
            INNER JOIN dbo.[Order] AS o ON o.OrderId   = ol.OrderId
            INNER JOIN dbo.Product AS p ON p.ProductId = ol.ProductId
            WHERE o.OrderDate >= @fromDate AND o.OrderDate < @toExclusive
            ORDER BY o.OrderDate DESC, ol.OrderLineId DESC
            OFFSET (@pageIndex * @pageSize) ROWS FETCH NEXT @pageSize ROWS ONLY;

            SELECT COUNT(*)
            FROM dbo.OrderLine AS ol
            INNER JOIN dbo.[Order] AS o ON o.OrderId = ol.OrderId
            WHERE o.OrderDate >= @fromDate AND o.OrderDate < @toExclusive;";

        using var conn = new SqlConnection(connectionString);
        using var grid = await conn.QueryMultipleAsync(sql,
            new { fromDate, toExclusive, pageIndex, pageSize });

        var items = (await grid.ReadAsync<OrderLineRow>()).ToList();
        var total = await grid.ReadSingleAsync<int>();
        return new PagedResult<OrderLineRow>(items, total, pageIndex, pageSize);
    }
}

// ---------- 2) El endpoint ----------
[ApiController]
[Route("api/reports")]
public class OrderReportController(OrderReportService service) : ControllerBase
{
    [HttpGet("order-lines")]
    public async Task<ActionResult<PagedResult<OrderLineRow>>> GetOrderLines(
        [FromQuery] DateTime fromDate,
        [FromQuery] DateTime toExclusive,
        [FromQuery] int pageIndex = 0,
        [FromQuery] int pageSize = 20)
    {
        if (toExclusive <= fromDate)
            return BadRequest("El rango de fechas no es valido.");

        pageIndex = Math.Max(0, pageIndex);
        pageSize  = Math.Clamp(pageSize, 1, 100);

        var result = await service.GetOrderLinesAsync(fromDate, toExclusive, pageIndex, pageSize);
        return Ok(result);
    }
}

// ---------- Tipos (si no me los dan, los defino yo) ----------
public record OrderLineRow(
    int OrderLineId, int OrderId, DateTime OrderDate,
    string ProductName, int Quantity, decimal UnitPrice, decimal LineTotal);

public record PagedResult<T>(IReadOnlyList<T> Items, int Total, int PageIndex, int PageSize);

Dossier de la empresa

Qué es

NEWCO LLC es la razón social; la marca es Vita Tienda de Cocó March (VitaTienda): tienda en línea de salud, bienestar y cuidado personal — suplementos, vitaminas, probióticos, kits, skincare, limpieza natural. Cara de marca: Dra. Cocó March (naturópata; las fórmulas se presentan como diseñadas por ella; claim de marketing "más de 1 millón de mujeres").

Mercado: hispano de EE. UU. + Latinoamérica. Operación bilingüe español/inglés, atención toda la semana. Es una empresa de e-commerce con equipo de desarrollo interno — no una empresa de tecnología. Footprint pequeño (12 seguidores en Computrabajo, 5 vacantes, sin reseñas de empleados).

Modelo de negocio y arquitectura probable

Lectura: lo más probable es que estén unificando PrestaShop + Shopify + Amazon detrás de un backend .NET propio (catálogo / inventario / pedidos como fuente de verdad), con Stripe para pago. "Integrar nuevos proveedores externos tomando ownership de toda la capa (back + front)" describe exactamente ese trabajo. Confírmalo preguntando (ver Preguntas → tus preguntas).

Stack (confirmado vs. inferido)

ÁreaTecnologíaConfianza
FrontendAngular 20.3, TypeScript, RxJS, Angular Material, BootstrapAlta (JD + notas)
Frontend arquitecturaMicrofrontends con Native Federation, federation.manifest.jsonAlta (notas)
Testing frontendKarma/Jasmine y JestAlta (notas)
Backend.NET / ASP.NET, REST APIAlta (JD)
Acceso a datosSQL Server (+ MySQL), escrito a mano — probablemente Dapper o ADO.NET, no EF pesadoMedia — preguntar cuál
Cloud / CIAzure, Azure DevOps (pipelines + boards)Alta (JD)
IntegracionesShopify Admin API, Amazon SP-API, StripeAlta (JD + notas)

Valores declarados (para las conductuales)

"Calidad, servicio cercano al cliente, experiencia de compra confiable, innovación en producto, excelencia en servicio." Traducción práctica: equipo chico, customer-obsessed, no romper producción (18 módulos vivos), alto impacto individual, ownership de punta a punta.

Entrevistadores probables

Sin datos públicos de LinkedIn (empresa no indexada). Espera 1–2 personas: quien redactó las notas (perfil técnico senior / tech lead, escribe "guion" y "pruebas técnicas" — lleva el proceso) y posiblemente alguien de producto u operación. Trata las notas como si vinieran del tech lead que decidirá.

Huecos conocidos de este dossier

SQL Server — la pregunta que decide

"Escribe a mano un join de tres tablas con filtro por fecha y paginación, y explica la transacción." Practica hasta que salga sin pensar. Abajo: el modelo, luego cada concepto que te van a picar.

Esquema de práctica (móntalo en tu SQL Server local y siembra datos)

CREATE TABLE dbo.Customer (
    CustomerId  INT IDENTITY PRIMARY KEY,
    FullName    NVARCHAR(120) NOT NULL,
    Email       NVARCHAR(160) NOT NULL
);
CREATE TABLE dbo.Product (
    ProductId   INT IDENTITY PRIMARY KEY,
    Sku         VARCHAR(32) NOT NULL UNIQUE,
    Name        NVARCHAR(160) NOT NULL,
    Stock       INT NOT NULL CONSTRAINT CK_Product_Stock CHECK (Stock >= 0)
);
CREATE TABLE dbo.[Order] (
    OrderId     INT IDENTITY PRIMARY KEY,
    CustomerId  INT NOT NULL REFERENCES dbo.Customer(CustomerId),
    OrderDate   DATETIME2(0) NOT NULL,
    Status      VARCHAR(20) NOT NULL
);
CREATE TABLE dbo.OrderLine (
    OrderLineId INT IDENTITY PRIMARY KEY,
    OrderId     INT NOT NULL REFERENCES dbo.[Order](OrderId),
    ProductId   INT NOT NULL REFERENCES dbo.Product(ProductId),
    Quantity    INT NOT NULL,
    UnitPrice   DECIMAL(10,2) NOT NULL
);
-- índices que justifican el WHERE y el ORDER BY
CREATE INDEX IX_Order_Date       ON dbo.[Order](OrderDate DESC, OrderId DESC) INCLUDE (CustomerId, Status);
CREATE INDEX IX_OrderLine_Order  ON dbo.OrderLine(OrderId);
CREATE INDEX IX_OrderLine_Product ON dbo.OrderLine(ProductId);

El JOIN de 3 tablas + fecha + paginación — modelo a memorizar

-- Renglones de pedido de un rango de fechas, con cliente y producto, paginado.
SELECT
    o.OrderId,
    o.OrderDate,
    c.FullName                     AS CustomerName,
    p.Sku,
    p.Name                         AS ProductName,
    ol.Quantity,
    ol.UnitPrice,
    ol.Quantity * ol.UnitPrice     AS LineTotal
FROM dbo.OrderLine AS ol
INNER JOIN dbo.[Order]  AS o ON o.OrderId    = ol.OrderId
INNER JOIN dbo.Customer AS c ON c.CustomerId = o.CustomerId
INNER JOIN dbo.Product  AS p ON p.ProductId  = ol.ProductId
WHERE o.OrderDate >= @FromDate
  AND o.OrderDate <  @ToDateExclusive          -- intervalo semiabierto: NO uses BETWEEN
ORDER BY o.OrderDate DESC, o.OrderId DESC, ol.OrderLineId DESC   -- orden determinista
OFFSET (@PageIndex * @PageSize) ROWS
FETCH NEXT @PageSize ROWS ONLY;

Lo que TIENES que decir mientras lo escribes (esto es lo que califican)

Si te piden el total de filas (para el paginador de la UI)
-- Opción A: dos queries en el mismo request (recomendado para páginas profundas)
SELECT COUNT(*)
FROM dbo.OrderLine ol
JOIN dbo.[Order] o ON o.OrderId = ol.OrderId
WHERE o.OrderDate >= @FromDate AND o.OrderDate < @ToDateExclusive;

-- Opción B: en la misma query con ventana (cuesta un scan del set filtrado)
SELECT ..., COUNT(*) OVER() AS TotalRows
FROM ... WHERE ... ORDER BY ... OFFSET ... FETCH ...;
La respuesta de escalado: paginación por keyset / seek (páginas profundas)

OFFSET grande es lento: el motor lee y descarta N filas. Para "cargar más" infinito o páginas profundas, keyset:

-- pasas el último (OrderDate, OrderId) de la página anterior
WHERE o.OrderDate >= @FromDate AND o.OrderDate < @ToDateExclusive
  AND (o.OrderDate < @LastDate
       OR (o.OrderDate = @LastDate AND o.OrderId < @LastOrderId))
ORDER BY o.OrderDate DESC, o.OrderId DESC
OFFSET 0 ROWS FETCH NEXT @PageSize ROWS ONLY;

Ventaja: coste constante por página, usa el índice directo. Desventaja: no puedes "saltar a la página 47".

La transacción — qué explicar

Lo más probable: una escritura de varios pasos (crear pedido + renglones + descontar stock), que es justo el tipo de operación de un POS / recepción electrónica.

CREATE PROCEDURE dbo.CreateOrder
    @CustomerId INT,
    @Lines      dbo.OrderLineTvp READONLY   -- table-valued parameter
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON;              -- error en runtime => rollback automático, sin transacciones "doomed"

    BEGIN TRY
        BEGIN TRANSACTION;

        INSERT INTO dbo.[Order] (CustomerId, OrderDate, Status)
        VALUES (@CustomerId, SYSUTCDATETIME(), 'Pending');

        DECLARE @OrderId INT = SCOPE_IDENTITY();

        INSERT INTO dbo.OrderLine (OrderId, ProductId, Quantity, UnitPrice)
        SELECT @OrderId, l.ProductId, l.Quantity, l.UnitPrice
        FROM @Lines AS l;

        -- descuento de stock con bloqueo, en una sola sentencia (evita race read-then-write)
        UPDATE p WITH (ROWLOCK)
            SET p.Stock = p.Stock - l.Quantity
        FROM dbo.Product AS p
        JOIN @Lines AS l ON l.ProductId = p.ProductId;

        -- el CHECK (Stock >= 0) ya protege; validación explícita para mensaje claro
        IF EXISTS (SELECT 1 FROM dbo.Product p JOIN @Lines l ON l.ProductId = p.ProductId WHERE p.Stock < 0)
            THROW 50001, 'Stock insuficiente para uno o más productos.', 1;

        COMMIT TRANSACTION;
        SELECT @OrderId AS OrderId;
    END TRY
    BEGIN CATCH
        IF XACT_STATE() <> 0 ROLLBACK TRANSACTION;
        THROW;   -- re-lanza; la capa .NET registra y traduce
    END CATCH
END

Conceptos que te van a picar sobre la transacción

ConceptoQué decir
ACIDAtomicidad (los 3 pasos se confirman juntos o ninguno), Consistencia (stock nunca negativo — CHECK + validación), Aislamiento (nivel configurable), Durabilidad (una vez COMMIT, sobrevive a caída).
SET XACT_ABORT ONCon esto, un error de runtime aborta y revierte toda la transacción automáticamente y evita dejar una transacción abierta/"doomed". Va al inicio del proc. Señal senior.
XACT_STATE() / @@TRANCOUNTComprobar en el CATCH antes de hacer ROLLBACK. XACT_STATE() = -1 = transacción no confirmable, solo rollback.
Niveles de aislamientoDefault READ COMMITTED. Para "leer stock y luego actualizar" el riesgo es lost update; se resuelve haciendo el descuento en una sola UPDATE (arriba), o con hints UPDLOCK, HOLDLOCK, o SERIALIZABLE. Menciona READ COMMITTED SNAPSHOT (RCSI) para reducir bloqueos de lectura.
DeadlocksOrden de acceso consistente a las tablas en todos los procs; reintento en error 1205 desde la capa de aplicación (Polly).
Concurrencia optimistaColumna rowversion comprobada en el WHERE del UPDATE; si afecta 0 filas, alguien más ya cambió el registro. En EF Core: atributo [Timestamp].
Transacciones cortasNada de llamadas HTTP dentro de la transacción. El cobro a Stripe / la llamada a Shopify va fuera; se coordina con idempotency key + patrón outbox / saga. Esto conecta directo con su trabajo de integraciones — dilo tú.

Acceso a datos "escrito a mano" — patrones (ellos: "la mitad del trabajo")

Dapper (lo más común para SQL a mano en .NET)

public async Task<IReadOnlyList<OrderLineRow>> GetLinesAsync(
    DateTime fromDate, DateTime toExclusive, int pageIndex, int pageSize, CancellationToken ct)
{
    const string sql = @"
        SELECT o.OrderId, o.OrderDate, c.FullName AS CustomerName,
               p.Sku, p.Name AS ProductName, ol.Quantity, ol.UnitPrice
        FROM dbo.OrderLine ol
        JOIN dbo.[Order]  o ON o.OrderId = ol.OrderId
        JOIN dbo.Customer c ON c.CustomerId = o.CustomerId
        JOIN dbo.Product  p ON p.ProductId = ol.ProductId
        WHERE o.OrderDate >= @fromDate AND o.OrderDate < @toExclusive
        ORDER BY o.OrderDate DESC, o.OrderId DESC, ol.OrderLineId DESC
        OFFSET (@pageIndex * @pageSize) ROWS FETCH NEXT @pageSize ROWS ONLY;";

    await using var conn = new SqlConnection(_connString);
    var cmd = new CommandDefinition(sql,
        new { fromDate, toExclusive, pageIndex, pageSize }, cancellationToken: ct);
    var rows = await conn.QueryAsync<OrderLineRow>(cmd);
    return rows.AsList();
}

// transacción con Dapper
await using var conn = new SqlConnection(_connString);
await conn.OpenAsync(ct);
await using var tx = await conn.BeginTransactionAsync(ct);
try
{
    var orderId = await conn.ExecuteScalarAsync<int>(insertOrderSql, param, tx);
    await conn.ExecuteAsync(insertLinesSql, lines, tx);
    await conn.ExecuteAsync(updateStockSql, lines, tx);
    await tx.CommitAsync(ct);
}
catch { await tx.RollbackAsync(ct); throw; }
ADO.NET crudo (por si lo piden sin librería)
await using var conn = new SqlConnection(_connString);
await conn.OpenAsync(ct);
await using var tx = (SqlTransaction)await conn.BeginTransactionAsync(ct);

await using var cmd = new SqlCommand(sql, conn, tx);
cmd.Parameters.Add("@fromDate", SqlDbType.DateTime2).Value = fromDate;
cmd.Parameters.Add("@toExclusive", SqlDbType.DateTime2).Value = toExclusive;
cmd.Parameters.Add("@pageIndex", SqlDbType.Int).Value = pageIndex;
cmd.Parameters.Add("@pageSize", SqlDbType.Int).Value = pageSize;

await using var reader = await cmd.ExecuteReaderAsync(ct);
while (await reader.ReadAsync(ct)) { /* map por índice ordinal */ }
await tx.CommitAsync(ct);

Puntos: Parameters.Add con tipo explícito (evita conversiones implícitas que rompen el índice), using/await using en todo, CancellationToken, mapear por ordinal para velocidad.

EF Core (por si usan un híbrido)
// consulta LINQ equivalente
var q = _db.OrderLines
    .Where(ol => ol.Order.OrderDate >= fromDate && ol.Order.OrderDate < toExclusive)
    .OrderByDescending(ol => ol.Order.OrderDate).ThenByDescending(ol => ol.OrderLineId)
    .Select(ol => new OrderLineRow { /* ... */ })
    .Skip(pageIndex * pageSize).Take(pageSize);

// transacción explícita
await using var tx = await _db.Database.BeginTransactionAsync(ct);
// ... SaveChangesAsync varias veces ...
await tx.CommitAsync(ct);

// SQL crudo dentro de EF
_db.Database.SqlQuery<OrderLineRow>($"SELECT ... WHERE OrderDate >= {fromDate}");

Di que EF SaveChanges ya es transaccional por llamada; la transacción explícita es para agrupar varias llamadas o mezclar con SQL crudo. Concurrencia optimista con [Timestamp] rowversion.

Mini-drills (hazlos a mano y luego córrelos)

  1. Top 10 productos más vendidos (por unidades) en un rango de fechas — GROUP BY + SUM + ORDER BY + TOP.
  2. Pedidos sin ningún renglón — LEFT JOIN … WHERE ol.OrderLineId IS NULL o NOT EXISTS.
  3. Total facturado por cliente y mes — GROUP BY c.CustomerId, DATEFROMPARTS(YEAR(o.OrderDate), MONTH(o.OrderDate), 1).
  4. Clientes con más de 3 pedidos en el rango — HAVING COUNT(*) > 3.
  5. El proc CreateOrder completo, probando el rollback (mete un producto con stock 1 y pide 5).
  6. Reescribe la paginación de OFFSET a keyset.
  7. Explica un plan de ejecución: dónde hay Index Seek vs Scan y por qué.

Angular a fondo — tu fuerte, hay que lucirlo

1 · Observable vs Promesa

PromiseObservable
Valoresuno solo0..∞ (un flujo en el tiempo)
Ejecucióneager: corre al crearselazy: corre al hacer subscribe()
Cancelablenosí: unsubscribe() aborta (p. ej. cancela el HTTP)
Operadores.then()/.catch()pipe(map, filter, switchMap, debounceTime, retry, combineLatest…)
Sync/asyncsiempre async (microtask)puede emitir sync o async
Multi-suscriptorcomparte el único resultadocold: cada subscribe reejecuta; hot/share(): multicast

En Angular: HttpClient devuelve Observable → al desuscribir cancela la petición (ideal para typeahead con switchMap). Eventos del Router, form.valueChanges, interop con Signals (toSignal(), toObservable()). El pipe async se suscribe y desuscribe solo.

Fugas de memoria = suscripciones sin gestionar. Soluciones: pipe async, takeUntilDestroyed() (Angular 16+), DestroyRef.

Los 4 operadores de aplanado — sé capaz de dar el caso de uso de cada uno

OperadorComportamientoCaso real en un POS / backoffice
switchMapcancela la anteriorbúsqueda de producto: solo importa la última tecla
mergeMaptodas en paralelodisparar N validaciones independientes a la vez
concatMapcola, en ordenguardar renglones de un pedido en secuencia sin pisarse
exhaustMapignora nuevas mientras trabajabotón "confirmar venta": evita doble submit

Extra: forkJoin (espera a que todas terminen — cargar datos de arranque), combineLatest (recombina al cambiar cualquiera — filtros), catchError + retry/retryWhen, shareReplay(1) (cachear la respuesta de un GET de catálogo).

2 · Native Federation — qué te aportó y qué te costó

Qué es: el concepto de Module Federation reconstruido sobre ESM nativo + import maps, para que funcione con el build moderno de Angular (esbuild/Vite). Webpack Module Federation dejó de encajar cuando Angular migró a esbuild; @angular-architects/native-federation es el reemplazo. La configuración de remotos vive en federation.manifest.json (el que tienes en tu repo).

Qué aporta (beneficios que viviste)

Qué cuesta (dolores concretos — esto es lo que confirma que lo usaste de verdad)

Prepárate a dibujarlo: shell (host) → lee federation.manifest.json → genera import map → carga remoteEntry de cada microfrontend bajo demanda → monta su ruta. Deps compartidas resueltas una vez.

3 · Otros temas que pueden caer (tu fuerte, respuestas cortas)

Testing — tu ventaja diferencial (Karma/Jasmine + Jest)

Eres el único del proceso que nombra las dos. Pregunta 3: "el último test de componente que escribiste y qué comprobaba." Necesitas una anécdota concreta y verificable. Rellena la plantilla con tu último test real.

Plantilla de tu anécdota — complétala con datos reales

  • Componente: [nombre] — hace [qué].
  • Por qué lo testeaba: [regresión / feature nueva / bug].
  • Qué comprobaba exactamente: [render condicional / emisión de @Output / que llamó al servicio con los args correctos / rama de error].
  • Cómo (setup): TestBed con [imports standalone], mocks con [jasmine.createSpyObj / { provide: X, useValue }].
  • Async: [fakeAsync+tick() / HttpTestingController / whenStable].
  • Coverage al que apuntabas: [%] — mencionaste que en tu repo apuntan a cobertura alta.

Karma/Jasmine — patrones que debes poder escribir

describe('SaleButtonComponent', () => {
  let fixture: ComponentFixture<SaleButtonComponent>;
  let sales: jasmine.SpyObj<SalesService>;

  beforeEach(async () => {
    sales = jasmine.createSpyObj<SalesService>('SalesService', ['confirm']);
    await TestBed.configureTestingModule({
      imports: [SaleButtonComponent],                 // standalone
      providers: [{ provide: SalesService, useValue: sales }],
    }).compileComponents();
    fixture = TestBed.createComponent(SaleButtonComponent);
  });

  it('deshabilita el botón mientras confirma', fakeAsync(() => {
    sales.confirm.and.returnValue(timer(100).pipe(map(() => ({ ok: true }))));
    fixture.componentInstance.total = 250;
    fixture.detectChanges();

    const btn = fixture.nativeElement.querySelector('button') as HTMLButtonElement;
    btn.click();
    fixture.detectChanges();
    expect(btn.disabled).toBeTrue();                  // durante la operación

    tick(100);
    fixture.detectChanges();
    expect(btn.disabled).toBeFalse();
    expect(sales.confirm).toHaveBeenCalledOnceWith(250);
  }));
});

HttpTestingController

TestBed.configureTestingModule({ imports: [HttpClientTestingModule] });
const http = TestBed.inject(HttpTestingController);
service.getCatalog().subscribe(r => expect(r.length).toBe(2));
const req = http.expectOne('/api/catalog');
expect(req.request.method).toBe('GET');
req.flush([{ id: 1 }, { id: 2 }]);
http.verify();

Component harnesses de Angular Material (@angular/cdk/testing) — señal fuerte, ellos usan Material

const loader = TestbedHarnessEnvironment.loader(fixture);
const button = await loader.getHarness(MatButtonHarness.with({ text: 'Confirmar' }));
await button.click();
const input = await loader.getHarness(MatInputHarness);
await input.setValue('ABC-123');

Ventaja: el test no depende del DOM interno de Material; sobrevive a upgrades.

Jest — por qué tienen los dos y en qué difiere

Karma/JasmineJest
Entornonavegador real (Chrome headless)jsdom (simulado en Node)
Velocidadmás lento (arranca navegador)rápido, paralelo, watch mode ágil
Spies/mocksjasmine.createSpyjest.fn(), jest.mock() de módulos
Snapshotsno nativo
Setup en Angularviene por defectojest-preset-angular

Por qué mantener ambos: Jest para el ciclo rápido de TDD y CI (miles de tests unitarios de lógica y componentes); Karma cuando necesitas fidelidad de navegador real — APIs del DOM que jsdom no implementa bien, layout, algunas cosas de Material/CDK, o pruebas que rozan lo e2e. Si te preguntan "¿los unificarías?": depende del coste de mantener dos configs vs. lo que se perdería; migrar a Jest todo lo unitario y dejar Karma/Playwright para lo que exige navegador es una postura defendible.

Testing en microfrontends

Tests de componente aislados: sin cambio. Lo difícil es la integración cruzando la frontera de federación — ahí conviene contract testing entre shell y remoto, o e2e (Playwright/Cypress) sobre el shell ensamblado.

Lo que digo — guion oral

Todas las frases, sin relleno. Una idea por línea, en el orden en que salen.

Las 3 reglas al hablar (feedback de tu ex jefe): 1 Directo — nada de "creo / me parece / no estoy seguro". 2 Con contexto — dónde lo usaste, qué problema, qué decidiste. 3 Término técnico exacto, no rodeos. Detalle y ejemplos en La prueba.

Al empezar (antes de teclear)

Base de datos

Backend — modelos y repositorio

Backend — controller y códigos HTTP

Probar la API antes del front

El JOIN — las 6 frases, en orden

  1. "Traigo columnas de las 4 tablas con alias claros; el total de línea lo calculo, Quantity * UnitPrice."
  2. "Arranco por OrderLine, la tabla base, el grano del reporte."
  3. "Le pego Order por OrderId, Customer por CustomerId, Product por ProductId; INNER porque solo quiero líneas completas."
  4. "Rango semiabierto: >= @fromDate AND < @toExclusive, nunca BETWEEN — incluiría el límite y se rompe con horas."
  5. "ORDER BY determinista que termina en columna única — OFFSET/FETCH lo exige o la paginación baila."
  6. "Paginación en el server con OFFSET/FETCH; el COUNT con el mismo WHERE en la misma ida."

La transacción (la otra mitad de la pregunta)

Frontend

Cierre — las 3 costuras

Cuando algo truena

Si preguntan por el tooling

Si mencionas algo que aquí no aplica

Cloud + Integraciones — AWS Lambda → Azure

Pregunta 4: "tu nube es AWS Lambda, no Azure: ¿qué esperas que cambie?" Buena señal si nombras lo que NO equivale. No finjas experiencia en Azure que no tienes; demuestra que sabes traducir conceptos y dónde están las trampas.

Tabla de equivalencias

AWSAzure¿Equivale limpio?
LambdaAzure FunctionsParcial — Functions usa bindings declarativos (input/output por atributos); en Lambda escribes las llamadas al SDK a mano. Modelo isolated worker en .NET.
API GatewayAzure API Management / HTTP trigger + Front DoorNo — APIM es más pesado y caro; para casos simples basta el HTTP trigger de la Function.
Step FunctionsDurable Functions / Logic AppsNo — Step Functions es una máquina de estados en JSON; Durable Functions es orquestación en código C#. Mentalidad distinta.
DynamoDBCosmos DBNo — Cosmos es multi-modelo, otro modelo de particionado y de costo (Request Units).
S3Blob StorageSí, cercano.
SQS / SNSStorage Queues / Service Bus / Event GridNo 1:1 — Service Bus ≈ SQS+SNS (colas + topics, sesiones, dead-letter); Storage Queues es lo simple; Event Grid es push de eventos. Son 3 cosas, no 1.
CloudWatchApplication Insights / Azure MonitorApp Insights es más rico para .NET (tracing distribuido casi gratis).
IAM rolesManaged Identity + RBAC (+ a veces App Registrations en Entra ID)No — otro modelo mental de identidad y permisos.
SAM / CDK / Serverless FrameworkBicep / ARM / azd, y Azure DevOps Pipelines (ellos)No — IaC y CI distintos; ellos ya usan Azure DevOps.

La respuesta modelo (di esto)

"Mi experiencia de nube es AWS Lambda, así que lo que traigo es el modelo serverless: funciones stateless, cold starts, pensar en idempotencia y en límites de ejecución. Al pasar a Azure, lo que traslada casi directo es el enfoque event-driven, Blob ≈ S3, y App Insights me daría más de lo que tenía con CloudWatch. Lo que no equivale y tendría que aprender: Azure Functions usa bindings declarativos en vez de llamar al SDK a mano; Durable Functions no es Step Functions —es orquestación en código, no una máquina de estados JSON—; Cosmos no es DynamoDB en particionado ni en costeo; y la identidad pasa de IAM roles a Managed Identity + RBAC. Además, para el grueso de una API .NET probablemente no sería Functions sino App Service o Container Apps — ¿qué usan ustedes?"

Esa última pregunta te posiciona como alguien que piensa en arquitectura, no solo en servicios sueltos.

Integraciones externas — patrones (esto es el corazón del puesto)

"Integrar nuevos proveedores externos tomando ownership de toda la capa." Ten estos patrones listos:

Amazon SP-API / Shopify / Stripe — lo mínimo a saber

Preguntas — biblia de ensayo

Cada pregunta: por qué la hacen, la estructura de tu respuesta, un borrador con tu dato marcado, qué recalcar y qué evitar. Practica en voz alta y cronometrada. No memorices palabra por palabra — con la estructura clara improvisas.

Las 3 reglas al hablar (feedback de tu ex jefe, aplican a toda respuesta): 1 · Seguridad — nada de "creo / siento / pienso / me parece"; esas muletillas dicen "no tengo experiencia real". 2 · Contexto — sitúa cada respuesta: dónde usaste esa tecnología, qué problema resolvía, qué decidiste. 3 · Lenguaje técnico — el término exacto, nunca un rodeo. Ejemplos en La prueba.

Lo que necesito de ti para cerrar los borradores (donde falta va [RELLENA]):

  • 3–4 logros concretos: proyecto · tu rol · impacto medible (tiempo/errores/volumen) · tecnología.
  • Tu frase real sobre la titulación (terminé el plan / me falta el trámite / estoy en X).
  • Tu nivel de inglés exacto (básico vs básico-intermedio) y qué haces para mejorarlo.

El método STAR (para las conductuales)

LetraQué esCuánto
Situación1 frase de contexto: dónde, cuándo, qué sistema.~10 s
Tareaqué tenías que lograr y por qué importaba.~10 s
Acciónlo que hiciste , en primera persona, concreto, con la decisión difícil.~40 s (el grueso)
Resultadoqué pasó, con número si puedes, y qué aprendiste / cambiaste.~15 s

Regla de oro: la A es donde te evalúan. Di "yo diseñé / yo decidí / yo escribí", nunca "el equipo hizo". 60–90 s por historia; si te pasas de 2 min, te cortan.

Parte 1 · Arranque

P · "Cuéntame de ti"

Por qué: marca el tono y te da el control de la narrativa. La pregunta de SQL viene justo después — este pitch la desactiva si nombras SQL Server tú mismo.

Estructura (4 movimientos, 60–90 s): quién eres + dónde → dónde tienes más profundidad (frontend) → tu otro lado (backend + nube, sin esconder nada) → qué buscas y por qué encajas.

"Soy desarrollador full-stack con 5 años en [SEUS], en un POS y backoffice para retail farmacéutico — hoy 18+ módulos en producción: compra directa, recepción electrónica, monitor de embarques.

Donde tengo más profundidad es en frontend: Angular —ahora en 20—, RxJS, Angular Material, y migramos a microfrontends con Native Federation, donde fui el mayor contribuidor del repo con 86 commits. Testeo con Karma/Jasmine y con Jest.

Del lado del backend trabajo .NET con REST y SQL Server, con el acceso a datos escrito a mano. Mi experiencia de nube es AWS Lambda.

Busco un puesto full-stack de verdad, remoto, con ownership de features completas — y su stack es casi el mismo en el que ya me muevo, así que contribuyo rápido y a la vez profundizo más en .NET y datos."

Recalca: di "5 años" (es lo del CV) · menciona SQL Server tú mismo · no escondas el inglés básico ni el AWS.

Evita: recitarlo monótono · pasar de 90 s · empezar por la vida personal · "soy apasionado de la tecnología".

P · "¿Por qué NEWCO / por qué este puesto?"

Por qué: filtran a quien aplica a todo. Quieren 2–3 razones específicas de ellos.

Estructura: el trabajo (encaja con cómo ya trabajas) → el stack (casi idéntico) → el producto (real, equipo chico, impacto) → dónde quieres crecer (su lado fuerte) → remoto.

"El modelo que describen —feature vertical completa, ownership de la capa de integración— es exactamente cómo ya trabajo. El stack coincide casi campo por campo: Angular 20, RxJS, Material, microfrontends; no pierdo tiempo de arranque. Es e-commerce con producto y usuarios reales, y equipo chico, o sea impacto directo. Y me interesa profundizar justo en .NET + SQL Server e integraciones —Stripe, Shopify, SP-API—, que es donde quiero crecer. Más que es remoto, que es lo que busco."

Evita: "me gusta su cultura" / "es una empresa en crecimiento" — vacío. Nada que aplique a cualquier empresa.

Parte 2 · Conductuales (STAR)

1 · "Cuéntame de una vez que tomaste ownership de algo de punta a punta"

Por qué: el puesto es eso: modelo de datos → servicio → endpoint → componente.

S: En el POS/backoffice farmacéutico, el módulo de [recepción electrónica / compra directa / monitor de embarques — elige uno].

T: necesitaba [RELLENA: qué problema de negocio resolver], y no había nadie más asignado.

A: Diseñé el modelo de datos en SQL Server, escribí el acceso a datos y el servicio .NET, expuse el REST y construí el componente Angular con Material. [RELLENA: 1 decisión técnica difícil — p. ej. denormalizar por rendimiento, un índice, cómo modelé el estado].

R: Salió a producción y [RELLENA: métrica — X horas/semana ahorradas, Y% menos errores, Z pedidos/día procesados]. Hoy es uno de los 18+ módulos vivos y le sigo dando mantenimiento.

Recalca: las cuatro capas, en primera persona. Evita: quedarte en "diseñé la arquitectura" sin bajar a una decisión concreta.

2 · "Un bug en producción: qué pasó y qué cambiaste para que no se repita"

Por qué: corren 18 módulos vivos; "experiencia de compra confiable" es un valor declarado.

S/T: [RELLENA: el bug — qué se rompió y a quién afectó].

A: detección ([¿monitoreo? ¿reporte de usuario?]) → aislé la causa raíz ([RELLENA]) → fix inmediato → y la parte que importa: agregué un test de regresión ([Karma / Jest]) y [cambio de proceso: una validación, una alerta, un paso de revisión en PR].

R: no volvió a pasar; el test lo cubre desde entonces.

Recalca: el 70% de la respuesta es la prevención, no el susto. Evita: culpar a otro; contar el bug con dramatismo y saltarte el "qué cambié".

3 · "Integración con una API externa que se portó mal"

Por qué: el core del puesto es integrar Stripe / Shopify / SP-API.

Si tienes la historia: [RELLENA: qué proveedor, qué falló — timeouts, rate limit, datos inconsistentes, webhooks duplicados] → cómo lo hiciste resiliente: reintentos con backoff, idempotency key, conciliación periódica.

Si NO la tienes: dilo y pivota — "proveedor de pagos como tal no he integrado; así lo abordaría" y explicas idempotency + outbox + webhooks firmados + circuit breaker (pestaña Cloud). Honesto y demuestra criterio.

Evita: inventar una integración que no hiciste — se cae con una repregunta.

4 · "Aprendiste algo nuevo bajo presión"

Por qué: quieren saber cómo entras a algo desconocido — como su codebase.

Historia natural: Native Federation. [RELLENA: por qué migraron a microfrontends, qué plazo tenías] → lo estudié desde los import maps y el manifest → migré [N] módulos → resolví los dolores reales: version skew (dos instancias de Angular, NG0203), comunicación entre microfrontends. R: deploy independiente por equipo.

Recalca: que nombras dolores concretos — eso prueba que lo hiciste, no que leíste el README.

5 · "Desacuerdo técnico con alguien del equipo"

Por qué: miden si eres rígido o colaborativo, y si cambias de opinión con datos.

S/T: [RELLENA: la decisión — p. ej. Karma vs Jest, estructura de un módulo, EF vs SQL a mano].

A: expuse mi posición con datos ([RELLENA: qué medí o comparé]) → escuché su argumento → [acordamos X / probamos un spike / cedí en Y porque tenía razón en Z].

R: [qué salió], y aprendí [qué de la otra postura].

Recalca: priorizas el resultado del equipo por encima de tener la razón. Evita: una historia donde tú tenías razón y ya.

6 · "Trabajar con producto o gente no técnica"

Por qué: el JD pide "traducir requerimientos de negocio a specs técnicas".

S/T: [RELLENA: un requerimiento ambiguo que te llegó].

A: hice preguntas para acotarlo ([qué preguntaste]) → lo convertí en una spec con casos y criterios de aceptación → lo validé con quien lo pidió antes de construir.

R: se construyó una vez, sin retrabajo.

Recalca: "pregunto antes de asumir" — es literal lo que evalúan en la prueba técnica también.

Parte 3 · Las incómodas (tu historial)

P · "No veo SQL en tu CV"

"Es cierto que no lo destaqué, y debí hacerlo: el acceso a datos en SQL Server es parte de mi día a día en el backend —consultas, joins, transacciones, todo escrito a mano—. Lo dejé implícito bajo '.NET / backend' y fue un error de redacción del CV, no de experiencia. De hecho por eso agradezco la prueba práctica."

Recalca: "error de redacción, no de experiencia" + agradecer la prueba. Evita: justificarte largo o sonar a la defensiva. Dos frases y adelante.

P · Tu nivel de inglés (te lo van a preguntar directo)

"Mi inglés es [básico / básico-intermedio — sé honesto] de lectura. Consumo documentación técnica en inglés —la de Angular ya así— y me apoyo en traducción con texto denso. No lo hablo con fluidez todavía; lo trabajo activamente [práctica diaria — Anki / una app]. Para comunicación escrita asíncrona me defiendo; para una reunión hablada en inglés todavía no. Sé que SP-API, Stripe y Angular están documentados en inglés y con eso puedo."

Evita: exagerar (lo testean en la misma llamada) · disculparte tres veces. Una respuesta clara + el plan de mejora.

P · Un solo empleador / pasante sin titular

Lo que dudanEncuadre (di esto, con tu verdad)
Un solo empleador toda la carrera"No cambié de empresa porque no dejé de crecer dentro: pasé de [rol inicial] a mayor contribuidor del repo y dueño de módulos críticos. Lo que sí cambió constantemente fue el stack debajo de mí — Angular 17→20, adoptar Native Federation, dos frameworks de test. Ahora busco ese cambio de entorno a propósito."
Pasante sin titular desde 2021Factual y hacia adelante: "[RELLENA tu situación real]. En estos años el trabajo real ha sido mi formación: 18 módulos en producción lo respaldan." No te disculpes, no te extiendas.

P · "¿Cómo te adaptas a un codebase nuevo?"

"Entré a un repo grande y en 4 años me volví su mayor contribuidor; sé leer código ajeno y moverme en un dominio complejo — retail farmacéutico con 18 módulos y sus reglas. Un codebase nuevo es la misma habilidad: primero leo el flujo de una feature de punta a punta, luego toco."

P · "¿Por qué dejas SEUS ahora?"

Cuatro razones, en positivo, sin quejarte del empleo actual: crecimiento (quiero profundizar en .NET/datos/integraciones) · ownership full-stack real · remoto · el stack encaja. Nunca "estoy harto de…" ni nada negativo de SEUS.

Parte 4 · Técnicas verbales (explicar, no escribir)

Las de escribir SQL están en SQL y Memoriza esto. Estas son para decirlas en < 1 min, con estructura.

P · "Observable vs Promesa" (+ los 4 operadores de aplanado)

Estructura: 3 diferencias que importan → dónde aparece en Angular → el riesgo y cómo lo manejas.

"Una promesa es un valor único, eager —corre al crearse— y no cancelable. Un Observable es un flujo perezoso: no hace nada hasta que te suscribes, puede emitir muchos valores en el tiempo, y lo cancelas con unsubscribe. En Angular importa porque HttpClient devuelve Observables: desuscribirte cancela la petición de verdad. Y tengo operadores —switchMap, debounceTime— para coordinar eventos. El riesgo es olvidar desuscribirte; lo resuelvo con el pipe async o takeUntilDestroyed."

Si preguntan por los 4 de aplanado: switchMap cancela el anterior (búsqueda) · mergeMap paraleliza · concatMap encola en orden (guardados) · exhaustMap ignora los nuevos mientras trabaja (botón confirmar, anti doble-submit).

P · "Native Federation: qué te aportó y qué te costó" (prepárate a dibujarlo)

Dibujo: shell (host) → lee federation.manifest.json → genera import map → carga el remoteEntry de cada microfrontend bajo demanda → monta su ruta. Deps compartidas (Angular, RxJS, Material) resueltas una vez.

Aporta: deploy independiente por microfrontend · integración en runtime · deps compartidas · compatible con esbuild (webpack MF ya no).

Cuesta (en 3 cubos): (1) version skew → dos instancias de Angular, NG0203, se mitiga con shared: { singleton: true, strictVersion: true } · (2) comunicación/estado entre microfrontends: sin solución nativa · (3) tooling: routing, dev local con todos los remotos, debugging opaco, testing cruzando la frontera, SSR más débil.

Recalca: nombrar los dolores concretos es lo que prueba que lo usaste de verdad.

P · "Tu nube es AWS Lambda, no Azure — ¿qué esperas que cambie?"

Estructura: lo que SÍ traslada → lo que NO equivale (aquí ganas puntos) → una pregunta de arquitectura.

"Lo que traigo es el modelo serverless: funciones stateless, cold starts, idempotencia, límites de ejecución. A Azure traslada casi directo el enfoque event-driven, Blob ≈ S3, y App Insights me daría más que CloudWatch. Lo que no equivale y tendría que aprender: Azure Functions usa bindings declarativos en vez de llamar al SDK a mano; Durable Functions no es Step Functions —es orquestación en código C#, no una máquina de estados JSON—; Cosmos no es DynamoDB en particionado ni en costeo; y la identidad pasa de IAM roles a Managed Identity + RBAC. Además, para una API .NET grande probablemente no sería Functions sino App Service o Container Apps — ¿qué usan ustedes?"

P · "El último test de componente que escribiste, y qué comprobaba" / Karma vs Jest

Tu anécdota (rellena con un test real): componente [nombre] → lo testeaba por [regresión / feature] → comprobaba [render condicional / emisión de @Output / que llamó al servicio con los args correctos] → setup con TestBed + mock con jasmine.createSpyObj → async con [fakeAsync+tick / HttpTestingController].

Karma vs Jest: Karma corre en navegador real (Chrome headless), Jest en jsdom (Node, más rápido). Tienen los dos: Jest para el ciclo rápido de TDD/CI, Karma para fidelidad de navegador real. "¿Los unificarías?" → depende del costo de mantener dos configs vs. lo que se perdería; postura defendible: todo lo unitario a Jest, Karma/Playwright solo para lo que exige navegador.

Parte 5 · Cierre

P · Expectativa de salario

Pregunta su rango primero, y el esquema (nómina / honorarios / contractor) y moneda. Si insisten en que des número: 65,000–85,000 MXN mensuales, ajustable según esquema y prestaciones. El piso que dices (65k) es tu objetivo, nunca menciones tu mínimo. Detalle completo en Negociación.

P · Disponibilidad / "¿cuándo puedes empezar?"

"Disponible de inmediato" ya lo tienen en el CV. Si sigues en SEUS, aclara tu tiempo de aviso real ([RELLENA: 2 semanas / 1 mes]).

P · "¿Dónde te ves en 2–3 años?"

"Más profundidad full-stack —sobre todo del lado de .NET, datos y arquitectura de integraciones—, y con el tiempo, si el equipo lo necesita, tomar decisiones técnicas de más alcance o mentorear." Concreto y alineado a un equipo chico. Nada de "tener tu puesto".

P · Preguntas cortas — ten una línea para cada una

  • ¿Mayor logro técnico? → una de tus historias STAR, la de ownership.
  • ¿Un error del que aprendiste? → la del bug en producción (con la prevención).
  • ¿Cómo priorizas cuando todo es urgente? → impacto en el cliente + reversibilidad; alineo con quien pidió.
  • ¿Cómo aseguras calidad? → tests (Karma+Jest), PRs con revisión, cobertura alta en el repo; y probar cada capa antes de subir.

P · "¿Tienes preguntas para nosotros?" — elige 4–6

Al tech lead:

  • "Del lado de .NET, ¿cómo escriben el acceso a datos — Dapper, ADO.NET, híbrido con EF? ¿Monolito modular o microservicios?"
  • "¿Qué servicios de Azure están realmente en uso — Functions, App Service, Container Apps? ¿App Insights?"
  • "Microfrontends: ¿cuántos remotos hay y cómo resuelven la comunicación / estado entre ellos?"
  • "PrestaShop + Shopify + Amazon: ¿hay migración en curso? ¿Cuál es la fuente de verdad de catálogo, inventario y pedidos?"
  • "Karma y Jest juntos: ¿criterio para usar uno u otro? ¿Plan de consolidar?"
  • "¿Cómo es el pipeline en Azure DevOps? ¿Con qué frecuencia despliegan a producción?"
  • "¿Qué fue lo último que se rompió en producción y por qué?"

Al hiring manager / producto:

  • "¿Cómo está estructurado el equipo? ¿Cuántos front, back, full-stack?"
  • "¿Cómo se ve el éxito en los primeros 90 días de esta persona?"
  • "¿Cómo trabaja el equipo con producto? ¿Quién define prioridades?"
  • "¿Qué tanto inglés se usa en el día a día — reuniones, PRs, docs?"
  • "Sobre la prueba técnica: ¿qué entorno debería tener listo, y cómo es el formato?"

La última resuelve el hueco del toolchain y del formato de la prueba — si no te lo han dicho, te lo dicen ahí.

Checklist de ensayo

  • ☐ Pitch grabado, suena natural (no recitado), < 90 s, nombras SQL Server tú mismo.
  • ☐ Las 6 historias STAR con tu dato real, cada una < 90 s, la A en primera persona.
  • ☐ "No veo SQL en tu CV" e inglés ensayadas — dos frases, sin disculparte de más.
  • ☐ Explicas Observable vs Promesa, los 4 operadores, Native Federation (dibujándolo) y AWS→Azure en < 1 min cada uno.
  • ☐ Anécdota de "el último test" con datos reales.
  • ☐ 4–6 preguntas elegidas para ellos, incluida la del entorno/formato.
  • ☐ Número: pides su rango primero; si insisten, 65–85k MXN.
  • ☐ Simulacro completo seguido: pitch → por qué NEWCO → SQL → Observable → Native Federation → test → AWS→Azure → inglés → historial → tus preguntas.

Negociación

Sobre los datos de banda salarial: Levels.fyi y similares casi no tienen data de e-commerce pequeño en México, y NEWCO es una LLC de EE. UU. contratando remoto en MX — el esquema (nómina local, honorarios, contractor en USD) cambia el número por completo. Lo de abajo es estimación de mercado, no un dato duro. Confírmalo preguntando su rango.

Tu marco (de tu perfil)

Estimación de mercado (remoto MX, full-stack Angular + .NET, ~5 años)

EscenarioRango mensual aprox. (MXN)
Nómina local, empresa no-tech pequeña40,000 – 65,000
Honorarios / contractor, pago influido por USD55,000 – 90,000+
Tu objetivo declaradohasta 80,000

Dado que es una LLC de EE. UU. y el puesto es "otro tipo de contrato" (probablemente honorarios/contractor), es razonable anclar en la parte alta.

Guion

Cuando pregunten "¿cuál es tu expectativa?"

"Antes de dar un número me ayudaría saber el esquema: ¿es nómina, honorarios, o contractor? ¿el pago es en pesos o en dólares? ¿qué prestaciones incluye? Con eso puedo darte una cifra que tenga sentido. ¿Cuál es el rango que tienen contemplado para la posición?"

Si insisten en que des número primero

"Con base en mi experiencia —5 años full-stack, Angular a nivel senior, microfrontends, y la parte de .NET y SQL Server que es justo lo que buscan— mi expectativa está en el rango de 65,000 a 85,000 MXN mensuales, ajustable según esquema y prestaciones."

El piso que dices (65k) es tu objetivo, no tu mínimo. Nunca ancles en 35k.

Contraoferta si ofrecen bajo

"Agradezco la oferta y el puesto me interesa mucho. El número está por debajo de lo que esperaba para este alcance —soy full-stack con ownership de la capa de integración y Angular senior—. ¿Hay margen para llegar a [tu cifra]? Si el fijo tiene tope, ¿podemos ver bono por desempeño, revisión a los 6 meses, o días de vacaciones?"

Qué preguntar antes de aceptar

Qué NO decir

Plan hasta el martes 8 sep, 8:00 am CDMX

Formato confirmado: fichero .cs suelto, 3 piezas (SQL + método + endpoint) + Angular hablado. No compila nada. El guion completo está en la pestaña La prueba; este plan es el calendario para dominarlo.

Bucle de práctica (Escritorio): doble clic en reset-prueba.bat → deja prueba.cs en blanco y lo abre. Tecleas la solución narrando, cronómetro corriendo. Corres verificar.sql en SSMS/Azure Data Studio contra PruebaCocoMarch para comprobar. Repites. reset-bd.bat si ensayaste INSERTs.

Hoy · lun 31 ago — Aterrizar

Mar 1 – mié 2 sep — La consulta SQL, hasta que salga sola

Jue 3 – vie 4 sep — Las 3 piezas completas + la voz

Sáb 5 – dom 6 sep — Angular hablado + lo no técnico

Lun 7 sep — Simulacro + logística

Checklist de logística

  • Desactiva la extensión "Claude Code" y cualquier Copilot en VS Code (Paso 0.1). Cierra pestañas de docs.
  • Prueba screen share + audio en la plataforma que usen (Meet/Zoom/Teams). Auriculares con micro.
  • ☐ Layout: editor a ⅔, chat de la reunión visible a ⅓ (o segundo monitor).
  • reset-prueba.bat deja prueba.cs listo. Ten a mano SSMS/Azure Data Studio conectado (para tu tranquilidad, aunque no lo uses en vivo).
  • ☐ Lugar sin ruido, buena luz, plan B de internet (hotspot).
  • ☐ 8:00 am CDMX = confirma tu zona horaria y pon 2 alarmas.

Mar 8 sep · 8:00 am — El día

Entorno — verificado

ComponenteEstado
.NET SDK✅ 9.0.317
SQL Server✅ 2022 Express, instancia localhost\SQLEXPRESS, corriendo
Clientes SQL✅ SSMS 19 · Azure Data Studio · sqlcmd 16
BD de prácticaPruebaCocoMarch — 3 tablas, poblada (10 productos, 20 pedidos, 37 líneas)
VS Code + C# Dev Kit✅ instalados (IntelliSense: abrir carpeta, no fichero suelto — poco relevante aquí porque no compila)
Scriptsdatos-prueba.sql · verificar.sql · prueba.cs · reset-prueba.bat · reset-bd.bat

Connection string para ensayar: Server=localhost\SQLEXPRESS;Database=PruebaCocoMarch;Trusted_Connection=True;TrustServerCertificate=True

Cheat-sheet — leer 10 min antes

Pitch en 3 bullets

  • 5 años full-stack · POS + backoffice retail farmacéutico · 18+ módulos en producción · mayor contribuidor del repo (86 commits).
  • Angular 20 senior: RxJS, Material, microfrontends con Native Federation. Testeo con Karma/Jasmine + Jest.
  • Backend .NET + REST + SQL Server con acceso a datos a mano. Nube: AWS Lambda. Busco full-stack real, remoto.

Por qué NEWCO

Ownership de feature vertical = como ya trabajo · stack 90% igual al mío · producto real, equipo chico, impacto directo · quiero profundizar en .NET + integraciones · remoto.

SQL — reflejos (dilos mientras escribes)

  • Fecha: >= @From AND < @ToExclusive — NUNCA BETWEEN. Nada de funciones sobre la columna.
  • Paginación: ORDER BY determinista + columna única de desempate, luego OFFSET/FETCH. Keyset para páginas profundas.
  • Parámetros siempre. Índice en (OrderDate DESC, OrderId DESC).
  • Transacción: SET XACT_ABORT ON + TRY/CATCH + IF XACT_STATE() <> 0 ROLLBACK + THROW.
  • ACID · aislamiento READ COMMITTED default · lost update → UPDATE en una sentencia o UPDLOCK · deadlock → orden consistente + retry 1205 · concurrencia optimista con rowversion.
  • Nada de HTTP dentro de la transacción → outbox + idempotency key.
  • A mano = Dapper: conn.QueryAsync<T>(sql, params), transacción con BeginTransactionAsync y pasar tx.

Observable vs Promise

Observable: flujo (0..∞) · lazy · cancelable · operadores · cold reejecuta. Promise: uno · eager · no cancelable · .then.
4 aplanados: switch=cancela (búsqueda) · merge=paralelo · concat=cola ordenada · exhaust=ignora mientras trabaja (submit).

Native Federation en una línea

Module Federation sobre ESM + import maps para esbuild. Aporta: deploy independiente, integración runtime, deps compartidas. Cuesta: version skew (dos Angular → NG0203), comunicación entre MFEs sin solución nativa, routing, dev local, debugging opaco.

AWS → Azure: lo que NO equivale

Step Functions ≠ Durable Functions (JSON vs código C#) · DynamoDB ≠ Cosmos (particionado, RU) · IAM roles ≠ Managed Identity + RBAC · API Gateway ≠ APIM · Lambda→Functions con bindings declarativos · una API .NET grande probablemente va en App Service / Container Apps, no Functions.

Inglés — una respuesta, sin disculparse de más

"Básico de lectura, consumo docs técnicos en inglés, lo trabajo activamente a diario, no conversacional aún. Para SP-API/Stripe/Angular me alcanza."

Historial laboral

Un empleador = crecí dentro sin dejar de hacerlo (rol inicial → mayor contribuidor + dueño de módulos). El stack cambió constantemente aunque la empresa no. Titulación: [tu frase real], factual y hacia adelante.

Número

Pregunta su rango + esquema (nómina/honorarios) + moneda PRIMERO. Si insisten: 65,000–85,000 MXN. Nunca menciones 35k.

Mis 3 preguntas top

  • ¿Cómo escriben el acceso a datos en .NET — Dapper, ADO, EF? ¿Monolito modular o microservicios?
  • ¿Cuántos microfrontends hay y cómo comparten estado? ¿Qué servicios de Azure usan realmente?
  • PrestaShop + Shopify + Amazon: ¿cuál es la fuente de verdad de catálogo/inventario/pedidos? ¿Hay migración en curso?

Antes de colgar / antes de la prueba

SQL Server + .NET SDK + Angular CLI ~20 montados y probados · solución scratch pre-creada · BD de práctica sembrada · screen share testeado · correo previo confirmando el entorno.