services/api/src/scripts/seed.js
Overview
Este script prepara datos locales para MongoDB. Crea o actualiza usuarios de prueba, genera anuncios, y rellena media faltante para que el backoffice tenga contenido visible. Está pensado para ejecutarse varias veces sin duplicar usuarios ni los anuncios del administrador .
[!IMPORTANT] Este seed solo escribe datos locales en MongoDB. No crea
CustomersniSubscriptionsde Stripe, y no requiere claves de Stripe. Las cuentas conplanpagado pueden existir sin datos de Stripe detrás. Eso es intencional para pruebas .
El archivo también carga services/api/.env desde su propia ubicación y luego hace una segunda carga de dotenv. Así mantiene la misma configuración aunque cambie el directorio de ejecución fileciteturn0file3turn0file7.
[!NOTE]
[email protected]es la cuenta seed principal.[email protected]y[email protected]son cuentas de prueba para anuncios.[email protected]no se crea aquí. Esa cuenta queda fuera del seed a propósito para probar Stripe e ImageKit reales .
How it works
flowchart TD
Start[Inicio]
Env[dotenv config]
Guard[evaluateSeedTarget]
Reject[Rechazar destino]
Connect[mongoose connect]
AdminPass[resolveSeedPassword]
AdminHash[bcrypt hash admin]
AdminUpsert[upsertSeedUser admin]
AdminListings[Insertar anuncios admin]
SofiaUpsert[upsertTestUser Sofia]
SofiaListing[ensureListing Sofia]
CarlosUpsert[upsertTestUser Carlos]
CarlosListing[ensureListing Carlos]
FixMedia[Actualizar media vacia]
Disconnect[mongoose disconnect]
End[Fin]
Start --> Env
Env --> Guard
Guard -->|no| Reject
Guard -->|si| Connect
Reject --> End
Connect --> AdminPass
AdminPass --> AdminHash
AdminHash --> AdminUpsert
AdminUpsert --> AdminListings
AdminListings --> SofiaUpsert
SofiaUpsert --> SofiaListing
SofiaListing --> CarlosUpsert
CarlosUpsert --> CarlosListing
CarlosListing --> FixMedia
FixMedia --> Disconnect
Disconnect --> End
El flujo empieza con la validación del destino. evaluateSeedTarget se ejecuta antes de conectar y bloquea destinos no locales. Si el destino no es aceptado, el script escribe los motivos, fija process.exitCode = 1 y termina sin conectar fileciteturn0file14turn0file3.
Si el destino es válido, el script conecta con mongoose. Después resuelve la contraseña del admin, genera su hash con bcrypt, y crea o actualiza [email protected] con findOneAndUpdate y upsert: true fileciteturn0file3turn0file7.
Luego completa los anuncios del administrador hasta llegar a 15, salvo que el admin legado ya conserve anuncios. Después asegura un anuncio para Sofia y otro para Carlos. Al final repara listings que no tienen media y desconecta Mongoose siempre, incluso tras errores fileciteturn0file3turn0file13.
Key components
Cada componente resuelve una parte concreta del seed. Juntos forman un flujo idempotente para desarrollo local fileciteturn0file3turn0file7.
{
"title": "resolveSeedPassword",
"content": "Resuelve la contrase\u00f1a del seed. Usa el valor de entorno si existe; si no, genera una temporal para esa corrida y no la imprime.",
"type": "function",
"filePath": "services/api/src/scripts/seed.js",
"badges": [
"public"
]
}
{
"title": "slugify",
"content": "Normaliza un texto para usarlo como semilla estable. Pasa a min\u00fasculas, elimina diacr\u00edticos, reemplaza caracteres no alfanum\u00e9ricos y recorta guiones extremos.",
"type": "function",
"filePath": "services/api/src/scripts/seed.js",
"badges": [
"public"
]
}
{
"title": "buildPlaceholderMedia",
"content": "Genera dos im\u00e1genes placeholder estables usando picsum.photos. La semilla sale del t\u00edtulo normalizado del anuncio.",
"type": "function",
"filePath": "services/api/src/scripts/seed.js",
"badges": [
"public"
]
}
{
"title": "buildListing",
"content": "Construye el documento de anuncio. Define precio, ubicaci\u00f3n, descripci\u00f3n, media, meta y estado publicado.",
"type": "function",
"filePath": "services/api/src/scripts/seed.js",
"badges": [
"public"
]
}
{
"title": "buildUser",
"content": "Construye la base de un documento de usuario. Une nombre y apellido, y aplica valores por defecto para systemRole y accountRole.",
"type": "function",
"filePath": "services/api/src/scripts/seed.js",
"badges": [
"public"
]
}
{
"title": "upsertSeedUser",
"content": "Busca un usuario por email y lo crea o actualiza. Inserta la identidad solo al crear, y corrige plan, roles y estado cuando ya existe.",
"type": "function",
"filePath": "services/api/src/scripts/seed.js",
"badges": [
"public"
]
}
{
"title": "upsertTestUser",
"content": "Crea o actualiza Sofia y Carlos. Si existe una contrase\u00f1a de entorno, la rehace; si no, conserva el hash existente cuando el usuario ya existe.",
"type": "function",
"filePath": "services/api/src/scripts/seed.js",
"badges": [
"public"
]
}
{
"title": "ensureListing",
"content": "Garantiza un anuncio m\u00ednimo para un usuario. Solo crea uno si el usuario no tiene anuncios previos.",
"type": "function",
"filePath": "services/api/src/scripts/seed.js",
"badges": [
"public"
]
}
{
"title": "run",
"content": "Orquesta todo el seed. Valida el destino, conecta MongoDB, prepara usuarios, crea anuncios, repara media y cierra la conexi\u00f3n.",
"type": "function",
"filePath": "services/api/src/scripts/seed.js",
"badges": [
"public"
]
}
{
"title": "evaluateSeedTarget",
"content": "Valida que el seed apunte a MongoDB local de desarrollo. El script no contin\u00faa si el destino es remoto, ambiguo o no interpretable.",
"type": "function",
"filePath": "services/api/src/scripts/lib/seedTargetGuard.js",
"badges": [
"public"
]
}
Data & fields
El script usa variables de entorno, plantillas de anuncios y objetos base de usuario. Estas estructuras determinan qué se crea y cómo queda guardado en MongoDB fileciteturn0file7turn0file15.
Variables de entorno
[
{
"name": "MONGODB_URI",
"type": "string",
"description": "URI de MongoDB. Si no existe, el script usa mongodb://127.0.0.1:27017/plano_backoffice."
},
{
"name": "NODE_ENV",
"type": "string",
"description": "Entrada adicional para la guardia de destino. Valores de producción o staging se rechazan."
},
{
"name": "APP_ENV",
"type": "string",
"description": "Entrada adicional para la guardia de destino. Valores de producción o staging se rechazan."
},
{
"name": "SEED_PASSWORD",
"type": "string",
"description": "Fuente principal de contraseña para [email protected]."
},
{
"name": "ADMIN_PASSWORD",
"type": "string",
"description": "Fallback de contraseña para [email protected] si SEED_PASSWORD no existe."
},
{
"name": "TEST_USER_SOFIA_PASSWORD",
"type": "string",
"description": "Contraseña opcional para [email protected]."
},
{
"name": "TEST_USER_CARLOS_PASSWORD",
"type": "string",
"description": "Contraseña opcional para [email protected]."
}
]
Identidades fijas del seed
[
{
"name": "ADMIN_EMAIL",
"type": "string",
"required": true,
"description": "Email fijo del admin seed. El archivo lo define como [email protected]."
},
{
"name": "LEGACY_ADMIN_EMAIL",
"type": "string",
"required": true,
"description": "Email legado usado solo para verificar si ya existen anuncios transferidos. El archivo lo exporta como [email protected]."
}
]
Plantillas de anuncios
ADMIN_LISTINGS, SOFIA_LISTING y CARLOS_LISTING comparten la misma forma. Cambian solo los valores concretos de cada inmueble fileciteturn0file3turn0file15.
[
{
"name": "title",
"type": "string",
"required": true,
"description": "Título visible del anuncio."
},
{
"name": "city",
"type": "string",
"required": true,
"description": "Ciudad del inmueble."
},
{
"name": "state",
"type": "string",
"required": true,
"description": "Estado o entidad del inmueble."
},
{
"name": "amount",
"type": "number",
"required": true,
"description": "Monto del anuncio en MXN."
},
{
"name": "op",
"type": "string",
"required": true,
"description": "Tipo de operación. En el archivo aparecen sale, presale, rent e investment_offer."
},
{
"name": "prop",
"type": "string",
"required": true,
"description": "Tipo de propiedad. El archivo usa house, apartment, land, commercial, office, warehouse y other."
},
{
"name": "bedrooms",
"type": "number",
"description": "Número de recámaras. Solo existe en algunas plantillas."
},
{
"name": "bathrooms",
"type": "number",
"description": "Número de baños. Solo existe en algunas plantillas."
}
]
buildUser input
[
{
"name": "firstName",
"type": "string",
"required": true,
"description": "Nombre del usuario."
},
{
"name": "lastName",
"type": "string",
"required": true,
"description": "Apellido del usuario."
},
{
"name": "email",
"type": "string",
"required": true,
"description": "Email único del usuario."
},
{
"name": "phone",
"type": "string",
"required": true,
"description": "Teléfono del usuario."
},
{
"name": "username",
"type": "string",
"required": true,
"description": "Username del usuario."
},
{
"name": "passwordHash",
"type": "string",
"required": true,
"description": "Hash de contraseña ya calculado."
},
{
"name": "systemRole",
"type": "string",
"description": "Rol de sistema. Por defecto es user."
},
{
"name": "accountRole",
"type": "string",
"description": "Rol de cuenta. Por defecto es creator."
}
]
buildListing output
[
{
"name": "title",
"type": "string",
"required": true,
"description": "Título del anuncio."
},
{
"name": "createdBy",
"type": "ObjectId",
"required": true,
"description": "Referencia al usuario dueño del anuncio."
},
{
"name": "price",
"type": "object",
"required": true,
"description": "Precio del anuncio.",
"children": [
{
"name": "amount",
"type": "number",
"required": true,
"description": "Cantidad en MXN."
},
{
"name": "currency",
"type": "string",
"required": true,
"description": "Moneda fija MXN."
}
]
},
{
"name": "location",
"type": "object",
"required": true,
"description": "Ubicación del inmueble.",
"children": [
{
"name": "city",
"type": "string",
"required": true,
"description": "Ciudad."
},
{
"name": "state",
"type": "string",
"required": true,
"description": "Estado."
},
{
"name": "country",
"type": "string",
"required": true,
"description": "País fijo Mexico."
}
]
},
{
"name": "description",
"type": "object",
"required": true,
"description": "Texto corto del anuncio.",
"children": [
{
"name": "short",
"type": "string",
"required": true,
"description": "Resumen corto."
},
{
"name": "caption",
"type": "string",
"required": true,
"description": "Leyenda visible."
}
]
},
{
"name": "media",
"type": "array",
"required": true,
"description": "Dos imágenes placeholder estables.",
"children": [
{
"name": "type",
"type": "string",
"required": true,
"description": "Siempre image."
},
{
"name": "src",
"type": "string",
"required": true,
"description": "URL de picsum.photos con semilla derivada del título."
},
{
"name": "alt",
"type": "string",
"required": true,
"description": "Texto alternativo igual al título."
}
]
},
{
"name": "meta",
"type": "object",
"required": true,
"description": "Metadatos del inmueble.",
"children": [
{
"name": "entityType",
"type": "string",
"required": true,
"description": "Siempre property."
},
{
"name": "operationType",
"type": "string",
"required": true,
"description": "Tipo de operación original."
},
{
"name": "propertyType",
"type": "string",
"required": true,
"description": "Tipo de propiedad original."
},
{
"name": "bedrooms",
"type": "number",
"required": true,
"description": "Número de recámaras o 0."
},
{
"name": "bathrooms",
"type": "number",
"required": true,
"description": "Número de baños o 0."
}
]
},
{
"name": "publication",
"type": "object",
"required": true,
"description": "Estado de publicación.",
"children": [
{
"name": "status",
"type": "string",
"required": true,
"description": "Siempre published."
}
]
}
]
Behaviors
Cada función tiene una responsabilidad concreta. El script compone esas piezas para mantener el seed repetible y predecible fileciteturn0file3turn0file9turn0file12.
resolveSeedPassword— devuelve una contraseña explícita o genera una temporal para la corrida actual.slugify— convierte un título en una semilla estable para URLs de imágenes.buildPlaceholderMedia— devuelve dos objetos de media con URLs deterministas depicsum.photos.buildListing— devuelve un documento plano listo paraListing.createoinsertMany.buildUser— devuelve un documento base de usuario conname,systemRoleyaccountRole.upsertSeedUser— crea o actualiza[email protected]sin sobrescribir datos personales existentes.upsertTestUser— crea o actualiza Sofia y Carlos, y puede rehacer el hash si recibe contraseña de entorno.ensureListing— garantiza al menos un listing por usuario, sin duplicar si ya existe alguno.run— ejecuta todo el flujo y fijaprocess.exitCodecuando la guardia falla.
[!NOTE]
upsertSeedUserusa$setOnInsertpara la identidad. En ejecuciones posteriores, solo corrigeplan,systemRole,accountRoleyaccountStatus. No pisa nombre, email, username, teléfono ni password de usuarios ya existentes fileciteturn0file7turn0file9.
[!WARNING]
upsertTestUsertrata distinto a los usuarios existentes. SienvPasswordno está definida, conserva el hash existente. También evita imprimir contraseñas temporales. Esa contraseña temporal solo se usa cuando el usuario aún no existe .
[!CAUTION]
ensureListingno intenta completar múltiples anuncios. Solo crea uno si el usuario no tiene ninguno. Si ya existe al menos un anuncio, no duplica registros .
Workflows
1. Cargar entorno | Lee services/api/.env y luego hace una segunda carga de dotenv.
2. Validar destino | Ejecuta evaluateSeedTarget antes de conectar.
3. Rechazar destino inseguro | Si no es local de desarrollo, escribe razones y termina sin conectar.
4. Conectar MongoDB | Abre mongoose con MONGO_URI.
5. Preparar admin | Hashea ADMIN_PASSWORD y crea o actualiza [email protected].
6. Completar anuncios | Inserta anuncios del administrador hasta 15, sin duplicar los existentes.
7. Asegurar cuentas demo | Crea o actualiza Sofia y Carlos y garantiza un anuncio para cada uno.
8. Reparar media | Busca listings sin media y les agrega imágenes placeholder.
9. Cerrar proceso | Desconecta mongoose al final, incluso si ocurrió un error.
El flujo es idempotente en varias partes. Los usuarios se reescriben por upsert, el admin se completa solo hasta 15 anuncios, y los listings vacíos reciben media de forma posterior. El cierre final siempre desconecta Mongoose en finally fileciteturn0file13turn0file4.
Gotchas & edge cases
[!WARNING] La guardia de destino bloquea cualquier MongoDB que no sea local de desarrollo. También rechaza destinos ambiguos o ilegibles. El mensaje de rechazo solo muestra hosts y base. Nunca imprime la URI completa ni credenciales fileciteturn0file14turn0file3.
[!IMPORTANT] El script no crea documentos en
admin_users. Tampoco recrea la cuenta legado de Aaron.LEGACY_ADMIN_EMAILsolo sirve para detectar si ya existen anuncios asociados a esa identidad .
[!NOTE]
[email protected]se crea consystemRole=admin. Sofia y Carlos se guardan consystemRole=user.buildUserdejaaccountRolepor defecto encreatorfileciteturn0file3turn0file7.
[!CAUTION] El script rellena listings sin
mediausando imágenes placeholder. Esa reparación solo actúa sobre anuncios existentes conmediaausente o vacía .
Related
Estos archivos participan directamente en este seed o reciben sus datos.
services/api/src/utils/passwords.js— aportahashPasswordpara Sofia y Carlos.services/api/src/models/User.js— recibe los upserts de usuarios.services/api/src/models/Listing.js— recibe los anuncios creados por el seed.services/api/src/scripts/lib/seedTargetGuard.js— valida que el destino sea local.services/api/.env— primera fuente de configuración para el script.services/api/package.json— exponenpm run seedcomo comando de ejecución
[!TIP] El comando del proyecto apunta directamente a
node src/scripts/seed.js. Eso convierte a este archivo en la entrada operativa del seed local fileciteturn0file16turn0file17