Ir al contenido

Sesión 2 · chuleta

En el orden de las slides. Copia con el botón de cada bloque; si la slide dice «teclea», esas líneas van a mano.

📋 S2·01 · receta «Cambiar de rama» a s2-start (terminal)

Sección titulada «📋 S2·01 · receta «Cambiar de rama» a s2-start (terminal)»
Ventana de terminal
git add -A && git commit -m "wip: mi trabajo" # guarda lo tuyo (si hay algo)
git fetch origin
git switch -c mi-s2 origin/s2-start # checkpoint: mi-s2-cp origin/s2-checkpoint
npm install
npx prisma generate
npx prisma migrate dev # desde tu rama pedirá reset (esperado): npx prisma migrate reset y npm run seed

📋 S2·02 · Category → Course (prisma/schema.prisma)

Sección titulada «📋 S2·02 · Category → Course (prisma/schema.prisma)»
model Course {
// ...campos de la sesión 1 (title, description, price, published, level, createdAt)
instructorId Int
instructor User @relation(fields: [instructorId], references: [id])
categoryId Int?
category Category? @relation(fields: [categoryId], references: [id])
}
model Category {
id Int @id @default(autoincrement())
name String @unique
courses Course[]
}

📋 S2·03 · enum EnrollmentStatus (prisma/schema.prisma)

Sección titulada «📋 S2·03 · enum EnrollmentStatus (prisma/schema.prisma)»
enum EnrollmentStatus {
ACTIVE
COMPLETED
CANCELLED
}

📋 S2·04 · migrar y generar el cliente (terminal)

Sección titulada «📋 S2·04 · migrar y generar el cliente (terminal)»
Ventana de terminal
npx prisma migrate dev --name add_enrollments
npx prisma generate

📋 S2·05 · src/seed-manual.ts completo (sustituye el de la S1)

Sección titulada «📋 S2·05 · src/seed-manual.ts completo (sustituye el de la S1)»
// src/seed-manual.ts — sustituye entero el de la S1
import { prisma } from './db.js';
const PLACEHOLDER = 'hashed-password-placeholder'; // sesión 4: Argon2id
async function upsertUser(email: string, name: string,
role: 'STUDENT' | 'INSTRUCTOR' | 'ADMIN') {
return prisma.user.upsert({
where: { email },
update: {},
create: { email, name, password: PLACEHOLDER, role },
});
}
async function main() {
const marta = await upsertUser('marta@academia.dev', 'Marta', 'INSTRUCTOR');
const students = await Promise.all([
upsertUser('luis@academia.dev', 'Luis', 'STUDENT'),
upsertUser('sara@academia.dev', 'Sara', 'STUDENT'),
upsertUser('omar@academia.dev', 'Omar', 'STUDENT'),
]);
const [backend, databases] = await Promise.all(
['Backend', 'Bases de datos'].map((name) =>
prisma.category.upsert({ where: { name }, update: {}, create: { name } }),
),
);
// Course no tiene campo único aparte del id:
// reseteamos los cursos de Marta (primero sus inscripciones, por el RESTRICT)
await prisma.enrollment.deleteMany({ where: { course: { instructorId: marta.id } } });
await prisma.course.deleteMany({ where: { instructorId: marta.id } });
// create, no createMany: necesitamos el id de cada curso
const ts = await prisma.course.create({ data: { title: 'TypeScript desde cero', price: 49.99,
published: true, level: 'BEGINNER', instructorId: marta.id, categoryId: backend.id } });
const prismaAdv = await prisma.course.create({ data: { title: 'Prisma avanzado', price: 79.99,
published: true, level: 'ADVANCED', instructorId: marta.id, categoryId: databases.id } });
await prisma.course.create({ data: { title: 'SQL para backend', price: 39.99,
published: false, level: 'INTERMEDIATE', instructorId: marta.id, categoryId: databases.id } });
// Inscripciones cubriendo los 3 estados (cada par usuario-curso es único)
await prisma.enrollment.createMany({
data: [
{ userId: students[0].id, courseId: ts.id, status: 'ACTIVE' },
{ userId: students[1].id, courseId: ts.id, status: 'COMPLETED', grade: 9 },
{ userId: students[2].id, courseId: prismaAdv.id, status: 'CANCELLED' },
],
});
console.log('Seed OK:', await prisma.course.count(), 'cursos,',
await prisma.enrollment.count(), 'inscripciones');
} // fin de main()
main().finally(() => prisma.$disconnect());
Ventana de terminal
npm run seed # Seed OK: 3 cursos, 3 inscripciones
npm run seed # Seed OK: 3 cursos, 3 inscripciones (otra vez, sin errores)

📋 S2·07 · esqueleto de src/playground/queries.ts

Sección titulada «📋 S2·07 · esqueleto de src/playground/queries.ts»
src/playground/queries.ts
import { prisma } from '../db.js';
async function main() {
// ✍️ a mano: las queries de esta sección, aquí dentro
}
main().finally(() => prisma.$disconnect());

📋 S2·08 · paginación offset (dentro de main en queries.ts)

Sección titulada «📋 S2·08 · paginación offset (dentro de main en queries.ts)»
const page = 1, pageSize = 2;
const offsetPage = await prisma.course.findMany({
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],
});

📋 S2·09 · andamiaje del todo o nada (al final de main en queries.ts)

Sección titulada «📋 S2·09 · andamiaje del todo o nada (al final de main en queries.ts)»
// añade al final de main() en src/playground/queries.ts
const fail = true; // paso 2: cámbialo a false
try {
await prisma.$transaction(async (tx) => {
// ✍️ a mano: dos tx.category.create y el throw
});
} catch (err) {
console.log('tx →', (err as Error).message);
}
const names = ['Frontend', 'DevOps'];
console.log('guardadas →', await prisma.category.count({ where: { name: { in: names } } }));
await prisma.category.deleteMany({ where: { name: { in: names } } }); // deja la base como estaba

📋 S2·10 · src/playground/s2-listado-y-aforo.ia.ts (código de IA)

Sección titulada «📋 S2·10 · src/playground/s2-listado-y-aforo.ia.ts (código de IA)»
// ─────────────────────────────────────────────────────────────────────────
// Generado por un asistente de IA · Sesión 2
// Prompt 1: "Lista los estudiantes con cuántas inscripciones tiene cada uno"
// Prompt 2: "Función para inscribir a un alumno respetando el aforo maxSeats"
// Tu tarea: (1) activa log de queries y cuenta cuántas lanza listStudents.
// (2) lee enrollStudent: ¿qué pasa si dos alumnos se inscriben a la vez?
// (lo comprobaréis con la demo src/playground/race.ts).
// ─────────────────────────────────────────────────────────────────────────
import { prisma } from '../db.js';
export async function listStudents() {
const students = await prisma.user.findMany({ where: { role: 'STUDENT' } });
const result = [];
for (const s of students) {
const enrollments = await prisma.enrollment.findMany({ where: { userId: s.id } });
result.push({ name: s.name, enrollments: enrollments.length });
}
return result;
}
export async function enrollStudent(userId: number, courseId: number) {
const course = await prisma.course.findUnique({ where: { id: courseId } });
const count = await prisma.enrollment.count({ where: { courseId } });
if (course && count < course.maxSeats) {
return prisma.enrollment.create({ data: { userId, courseId } });
}
throw new Error('Course full');
}

📋 S2·11 · cliente que cuenta queries (inicio de src/playground/list-students.ts)

Sección titulada «📋 S2·11 · cliente que cuenta queries (inicio de src/playground/list-students.ts)»
src/playground/list-students.ts
import 'dotenv/config';
import { PrismaPg } from '@prisma/adapter-pg';
import { PrismaClient } from '../../generated/prisma/client.js'; // dos ../
const prisma = new PrismaClient({
adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),
log: [{ emit: 'event', level: 'query' }],
});
let queries = 0;
prisma.$on('query', () => { queries++; });

📋 S2·12 · traer del checkpoint solo los ficheros que no has escrito (terminal)

Sección titulada «📋 S2·12 · traer del checkpoint solo los ficheros que no has escrito (terminal)»
Ventana de terminal
git fetch origin
git checkout origin/s2-checkpoint -- src/services src/errors.ts src/playground/race.ts src/playground/n-plus-one.ts

📋 S2·13 · receta «Cambiar de rama» a s2-checkpoint (terminal)

Sección titulada «📋 S2·13 · receta «Cambiar de rama» a s2-checkpoint (terminal)»
Ventana de terminal
git add -A && git commit -m "wip: mi trabajo" # guarda lo tuyo (si hay algo)
git fetch origin
git switch -c mi-s2-cp origin/s2-checkpoint # mi-s2 ya existe: otro nombre
npm install
npx prisma generate
npx prisma migrate dev # desde tu rama pedirá reset (esperado): npx prisma migrate reset y npm run seed

📋 S2·14 · measure y main (sigue en src/playground/list-students.ts)

Sección titulada «📋 S2·14 · measure y main (sigue en src/playground/list-students.ts)»
// sigue en src/playground/list-students.ts
async function measure(label: string, fn: () => Promise<unknown>) {
queries = 0;
const result = await fn();
console.log(`${label}: ${queries} queries`, JSON.stringify(result));
}
// al final del fichero, como en queries.ts
async function main() {
await measure('IA (bucle)', listStudents); // + una por versión tuya
}
main().finally(() => prisma.$disconnect());