Saltar a contenido

🛠 Unidad ISS-04 · Seeders con Faker — capa 🛠 CONSTRUIR

🧠 Comprender este bloque → · 📝 Evaluación · ✅ GATE de la unidad

Capa Página Para qué
🧠 Aprender Seeders con Faker comprender, explicar y relacionar
🛠 Construir esta página ejecutar, programar y verificar
✅ GATE Condiciones de cierre condición para pasar al bloque siguiente

Esta es la guía ejecutable. Sigue los pasos en orden. El cuerpo de abajo es el ISS técnico verbatim: comandos, rutas, versiones, PARCHEs, verificaciones y criterios, sin simplificar ni modernizar.


Fase I: Business — ISS-04 — Seeders con Faker (feature + runner)

Contexto mínimo (autosuficiente). Si abres este archivo aislado —o lo llevas a otra IA—, esto es lo que hay que saber antes de ejecutarlo. - Proyecto: app-storelab-express-ii — Express 5 + TypeScript + Sequelize, arquitectura por features, Fase I solo Business (sin auth ni roles). - Recorrido obligatorio de una petición: HTTP → Controller → Service → Repository → Model → Sequelize → BD. Ninguna capa se salta a la siguiente. - Diseño de datos (columnas, tipos, FK RESTRICT, índices, transacciones): ../bd-storelab.md, Fase I — Business. - Capas, convenciones y reglas transversales: 00-contexto.md.

Este ISS
Título Seeders con Faker (feature + runner)
Feature / tabla clients (seeder)
API npm run db:seed
Depende de ISS-03 — Feature Client (CRUD por capas)
Habilita ISS-06 — Feature ProductType

Contenido de este ISS

  • 9.1 Seeder dentro del feature Client
  • 9.2 SeedersRunner + conteos por entidad (database/seeders)

Objetivo: datos falsos por feature (Faker) y un orquestador externo que ejecuta todos los seeders enviando la cantidad por entidad.
Bloqueado por: ISS-03-A (modelo); recomendado tras ISS-03-E — ambos en ISS-03.

Criterios de aceptación (ISS-04) — consolidados

  • [ ] 9.1 Existe features/business/clients/clients.seeder.ts con @faker-js/faker, recibe count, es idempotente
  • [ ] 9.2 Existe database/seeders/index.ts (SeedersRunner) que llama seeders de features
  • [ ] 9.2 Existe database/seeders/counts.ts con cantidad por entidad (default / env / CLI)
  • [ ] Script npm run db:seed funciona
  • [ ] Se puede variar cantidad: npm run db:seed -- --clients=20 o SEED_CLIENTS=5

Diseño

Pieza Ubicación Rol
Seeder del feature src/features/business/clients/clients.seeder.ts Genera filas falsas de Client
Conteos src/database/seeders/counts.ts clients: N (y futuras entidades)
Runner src/database/seeders/index.ts Importa seeders de features y los ejecuta en orden

Los seeders son scripts, no la API. Viven fuera del recorrido HTTP → Controller → Service → Repository: se ejecutan por consola (npm run db:seed) y escriben con el modelo directamente. Por eso no usan los DTOs ni el service, y cuando necesitan una regla del dominio la replican y la documentan (ej. product-sales.seeder.ts recalcula subtotal/total con la misma fórmula que recalculateSaleTotals). Así el código de la API no arrastra dependencias de scripts.


9.1 Seeder dentro del feature Client

Criterios

  • [ ] seedClients(count: number) exportado desde el feature
  • [ ] Usa @faker-js/faker
  • [ ] Si ya hay filas, no duplica
npm install -D @faker-js/faker@^10.6.0
: > src/features/business/clients/clients.seeder.ts
cat >> src/features/business/clients/clients.seeder.ts << 'EOF'
import { faker } from "@faker-js/faker";
import { Client } from "./client.model";

/**
 * Seeder del feature Client (datos falsos con @faker-js/faker).
 * Se invoca desde `src/database/seeders` (SeedersRunner), no desde la App.
 *
 * Idempotente: si ya hay filas, no vuelve a insertar.
 */
export async function seedClients(count: number): Promise<number> {
  if (count <= 0) {
    console.log("⏭️  clients: count=0, se omite");
    return 0;
  }

  const existing = await Client.count();
  if (existing > 0) {
    console.log(`⏭️  clients: ya hay ${existing} registro(s), se omite seeder`);
    return 0;
  }

  const rows = Array.from({ length: count }, (_, i) => ({
    name: faker.person.fullName(),
    address: faker.location.streetAddress(),
    phone: faker.phone.number({ style: "national" }),
    email: `client.${i}.${faker.string.alphanumeric(6)}@example.com`.toLowerCase(),
    password: "Password123!",
    status: "active" as const,
  }));

  await Client.bulkCreate(rows);
  console.log(`✅ clients: insertados ${count} registro(s) falsos`);
  return count;
}
EOF

9.2 SeedersRunner + conteos por entidad (database/seeders)

Criterios

  • [ ] Runner fuera del feature en src/database/seeders/
  • [ ] Cantidad configurable por feature (clients, …)

9.2.1 Conteos

: > src/database/seeders/counts.ts
cat >> src/database/seeders/counts.ts << 'EOF'
/**
 * Cantidad de registros por feature/entidad.
 * Prioridad: CLI (--clients=N) > env (SEED_CLIENTS) > default de este archivo.
 *
 * Cuando agregues features, suma aquí la clave y léela en el runner.
 */
export type SeedCounts = {
  clients: number;
  // users?: number;
  // roles?: number;
  // products?: number;
};

export const DEFAULT_SEED_COUNTS: SeedCounts = {
  clients: 10,
};

export function resolveSeedCounts(argv: string[] = process.argv.slice(2)): SeedCounts {
  const counts: SeedCounts = { ...DEFAULT_SEED_COUNTS };

  const envClients = process.env.SEED_CLIENTS;
  if (envClients !== undefined && envClients !== "") {
    counts.clients = Number(envClients);
  }

  for (const arg of argv) {
    const m = arg.match(/^--([a-zA-Z_]+)=(\d+)$/);
    if (!m) continue;
    const key = m[1] as keyof SeedCounts;
    const value = Number(m[2]);
    if (key in counts) {
      counts[key] = value;
    }
  }

  return counts;
}
EOF

9.2.2 Runner

: > src/database/seeders/index.ts
cat >> src/database/seeders/index.ts << 'EOF'
import dotenv from "dotenv";
import { sequelize, testConnection } from "../db";
import "../../features/business/clients/client.model";
import { seedClients } from "../../features/business/clients/client.seeder";
import { resolveSeedCounts } from "./counts";

dotenv.config();

/**
 * SeedersRunner — ejecuta TODOS los seeders de features.
 *
 * Ubicación: `src/database/seeders/` (orquestación fuera de cada feature).
 * Cada feature exporta su seeder (ej. `features/business/clients/clients.seeder.ts`).
 *
 * Uso:
 *   npm run db:seed
 *   npm run db:seed -- --clients=20
 *   SEED_CLIENTS=5 npm run db:seed
 */
export async function runAllSeeders(): Promise<void> {
  const counts = resolveSeedCounts();
  console.log("🌱 Iniciando SeedersRunner...");
  console.log("📊 Conteos:", counts);

  const ok = await testConnection();
  if (!ok) {
    throw new Error("No hay conexión a la base de datos");
  }

  await sequelize.sync({ force: false, alter: true });

  // Orden: business (padres → hijos)
  await seedClients(counts.clients);

  console.log("🌱 SeedersRunner finalizado");
}

if (require.main === module) {
  runAllSeeders()
    .then(async () => {
      await sequelize.close();
      process.exit(0);
    })
    .catch(async (err) => {
      console.error("❌ Error en seeders:", err);
      await sequelize.close();
      process.exit(1);
    });
}
EOF

PARCHE — package.json ya existe.

Dentro de "scripts", debajo de "dev": "...", añadir la coma al final de dev (si falta) y la clave:

    "db:seed": "ts-node -- src/database/seeders/index.ts"

Fragmento esperado:

  "scripts": {
    "build": "tsc",
    "dev": "nodemon --watch src --ext ts --exec ts-node -- src/server.ts",
    "db:seed": "ts-node -- src/database/seeders/index.ts"
  }

Verificación ISS-04

npm run db:seed
npm run db:seed -- --clients=20
SEED_CLIENTS=5 npm run db:seed

Al agregar otra entidad (patrón):

  1. Archivo nuevo features/.../<plural>.seeder.ts con : > + cat >>.
  2. PARCHE counts.ts: dentro de SeedCounts / defaults, añadir clave (ej. products: 10).
  3. PARCHE database/seeders/index.ts: debajo de await seedClients(...), añadir la llamada al nuevo seeder.

Cierre del ISS

npm run dev

El servidor debe arrancar sin error. Detenerlo con Ctrl+C antes de continuar.


✅ GATE de la unidad ISS-04 — este bloque no añade ningún criterio nuevo.

Las condiciones de cierre son exactamente las que define el ISS técnico de esta misma página:

Con el GATE en verde queda habilitado ISS-05 · Swagger / OpenAPI.

Navegación de la ruta: ← ISS-04 · 🧠 Aprender · ↑ Ruta Express · → ISS-05 · 🧠 Aprender