Entorno de desarrollo local reproducible con datos semilla y fixtures
Configura un entorno de desarrollo local reproducible con datos seed y fixtures para que cualquiera pueda ejecutar la app localmente con la misma base de datos, de forma rápida y fiable.

Por qué las bases de datos locales se vuelven impredecibles
Una base de datos local empieza limpia el primer día. Luego todos la tocan. Un compañero ejecuta una migración, tú importas un CSV, alguien prueba una función que crea 2.000 filas, y de repente la app se comporta distinto en cada portátil.
Así es como un entorno de desarrollo local reproducible se rompe sin ruido. El código puede ser el mismo, pero los datos no lo son.
Cuando cada desarrollador tiene datos locales diferentes, las cosas pequeñas se vuelven confusas rápido. Una pantalla que a una persona le funciona muestra errores a otra. La búsqueda parece “lenta” solo en una máquina. Un flujo de registro “funciona” solo porque una base de datos ya tiene los roles, flags o cuentas de demostración correctas.
Muchos "me funciona en mi máquina" empiezan en la base de datos porque esta lleva estado oculto: migraciones aplicadas en distinto orden, filas creadas por experimentos antiguos, registros faltantes que la app asume que existen (como un usuario admin o ajustes por defecto), e incluso secretos o claves API que acabaron en el lugar equivocado.
La configuración manual se convierte en un cuello de botella tan pronto como incorporas a alguien nuevo o necesitas un reinicio limpio para rastrear un bug. Si tu doc de setup incluye pasos como “crea estos 12 registros a mano” o “pide a alguien un volcado de la base de datos”, fallará tarde o temprano.
Los datos seed son los datos base que tu app necesita para ejecutarse localmente (unos pocos usuarios, planes y feature flags). Las fixtures son pequeños conjuntos de datos específicos pensados para cubrir un escenario (como “usuario con suscripción expirada” o “pedido con reembolso”) para que puedas probar una pantalla o API de forma fiable.
Si heredaste un prototipo generado por IA que “casi funciona”, aquí suele ser donde falla. Verás una app que solo corre en la máquina del creador original porque la base de datos nunca se hizo reconstruible. La solución empieza haciendo los datos predecibles, no adivinando lo que falta.
Datos seed y fixtures: qué usar y cuándo
Un entorno de desarrollo local reproducible empieza con una decisión: ¿quieres ayudar a la gente a usar la app manualmente, o verificar el comportamiento automáticamente? Esa elección te dirá si necesitas datos seed, fixtures, o ambos.
Piénsalo así:
- Las migraciones cambian la forma de la base de datos (tablas, columnas, índices). No deberían depender de usuarios de ejemplo o pedidos de muestra.
- Los datos seed dan a los desarrolladores una línea base predecible para que la UI no esté vacía y los flujos comunes sean fáciles de probar.
- Las fixtures dan a los tests entradas conocidas para que las comprobaciones automáticas se ejecuten igual cada vez.
- Las factories (opcionales) crean registros bajo demanda, a menudo como una alternativa más flexible a las fixtures estáticas.
Mantén separadas la base de datos de dev y la de tests. Los datos de desarrollo son para personas que exploran funciones. Los datos de test son para automatización y deben estar aislados, reiniciarse con frecuencia y ser seguros para ejecutarse en paralelo. Mezclarlas es como conseguir tests que pasan en un portátil y fallan en todos los demás.
Cuando elijas qué debe parecer “real”, busca realismo donde afecte la lógica, no donde añada ruido. Mantén roles, permisos y estados de casos límite realistas, pero usa nombres falsos, emails y datos de pago de ejemplo.
Una regla práctica es simple: sembrar un pequeño número de flujos completos (2–3 usuarios, 1 org, un puñado de registros por pantalla). Añade uno o dos casos intencionalmente raros (token expirado, cuenta desactivada, estado vacío) para cubrir ramas de la UI. Evita blobs enormes como imágenes o logs gigantes; usa stubs que aún desencadenen las mismas rutas de código. Nunca siembres secretos. Y cuando ayude, haz IDs y timestamps deterministas para que las capturas de pantalla y la depuración coincidan entre máquinas.
Diseña un setup local que puedas reconstruir en cualquier momento
Un setup solo es “simple” si puedes borrarlo todo y volver a un app funcional sin recordar pasos secretos. Esa es la esencia de un entorno de desarrollo local reproducible: una máquina nueva debería comportarse como la tuya.
Elige un comando que cree la base de datos desde cero y haz que sea seguro ejecutarlo repetidamente, incluso si la base de datos ya existe. Los nuevos contribuyentes no deberían tener que adivinar qué scripts ejecutar en qué orden.
Los comandos de reset más fiables hacen lo mismo cada vez: limpiar o recrear la base de datos local, ejecutar migraciones, cargar datos seed para desarrollo cotidiano, arrancar servicios requeridos y mostrar el siguiente paso (incluyendo cómo iniciar sesión).
Mantén la fuente de verdad del esquema en las migraciones, no en SQL editado a mano en una wiki o en comandos “arreglar” de una sola vez que la gente ejecuta una vez. Si alguien cambia una tabla localmente y olvida capturarla en una migración, tendrás bugs misteriosos donde “me funciona en mi portátil” se vuelve normal.
Crea un usuario de base de datos local dedicado con permisos limitados. Suena a trabajo extra, pero detecta errores reales temprano. Por ejemplo, si tu app intenta crear tablas en tiempo de ejecución por accidente, un usuario con permisos limitados fallará rápido en lugar de ocultar el problema hasta producción.
Los servicios opcionales son donde los setups suelen ensuciarse. Decide qué es obligatorio y qué es agradable de tener. Si Redis, S3 o email son opcionales, haz que la app arranque sin ellos y muestre un mensaje claro cuando una función no esté disponible. Un enfoque común es soportar versiones locales “falsas” (almacenamiento de archivos en lugar de S3, una bandeja local en lugar de email) y habilitar integraciones reales solo cuando un desarrollador opte por ello.
Escribe scripts de seed que siempre produzcan los mismos datos
Un script de seed solo ayuda si es aburrido. Ejecútalo hoy, la próxima semana o en un portátil nuevo, y deberías obtener los mismos registros, los mismos accesos y las mismas pantallas demo.
Elige un orden de siembra fijo basado en dependencias. Si los proyectos pertenecen a orgs, y las orgs pertenecen a usuarios, siembra usuarios primero, luego orgs, luego proyectos, y después todo lo que esté ligado a proyectos (tareas, facturas, comentarios). Esto evita errores de “padre faltante” y mantiene las claves foráneas consistentes.
Usa identificadores estables o claves naturales para que los datos no se desvíen. Cualquier cosa a la que te refieras después debería tener un identificador permanente (email de usuario, slug de org, o un UUID fijo que incluyas en el seed). Evita nombres aleatorios, UUIDs cambiantes o “insertar y esperar que tenga id 3”, porque las reejecuciones y distintos motores de BD cambiarán los resultados.
Haz el script idempotente, es decir, que pueda volver a ejecutarse sin crear duplicados. En lugar de insertar siempre, haz upsert por la clave natural (email, slug, external_id). Si necesitas un reset completo, que sea intencional (borrar tablas primero, o soportar una bandera --reset), no accidental.
También ayuda mantener las configuraciones de seed en un solo lugar: contadores (cuántas orgs/proyectos/registros), feature flags, credenciales demo fijas y cualquier toggle de entorno (como seed rápido vs full seed). Cuando esos valores están dispersos, cada máquina termina “casi igual, pero diferente”.
Construye fixtures que cubran casos reales de UI y API
Las fixtures son pequeños conjuntos de datos conocidos que cargas para que pantallas y endpoints se comporten igual siempre.
Empieza con unos pocos registros realistas ligados a tus flujos más usados. Piensa en clics: iniciar sesión, aterrizar en un dashboard, ver una lista, abrir una página de detalle, guardar un cambio. Si tu app tiene organizaciones y proyectos, una org con dos proyectos, un par de tareas y un elemento de actividad reciente suele ser suficiente para activar gran parte de la UI y las rutas de API sin crear un dataset gigante.
Luego añade casos límite a propósito. No necesitas muchos, pero sí los que suelen romper:
- Un estado vacío (una org nueva sin proyectos)
- Un nombre largo (maquetación y truncamiento)
- Un usuario deshabilitado (acceso y mensajes)
- Campos opcionales faltantes (manejo de null)
- Un límite de permisos (puede ver pero no editar)
Mantén las fixtures fáciles de leer y de diff en una revisión de código. YAML, JSON o un pequeño archivo TypeScript está bien. Elige un estilo y mantente con él. Usa IDs y timestamps estables cuando sea posible, para que snapshots, ordenamientos y widgets de “actividad reciente” no cambien al azar.
Finalmente, documenta la intención donde viven los datos. Un comentario corto como “Usado para la página de Ajustes — estado vacío” ahorra tiempo después.
Paso a paso: un comando que lo deja todo listo
Un entorno de desarrollo local reproducible se siente casi sin esfuerzo al incorporar a alguien: ejecuta un comando y no necesita pasos especiales de otra persona.
Tu comando único debería hacer la misma secuencia cada vez:
- Resetear la base de datos local de forma segura. Usa un nombre de BD dedicado para dev y una comprobación de seguridad llamativa (por ejemplo, negarse a ejecutarse si
NODE_ENV=production). - Aplicar las migraciones desde cero. El esquema solo debería crearse a través de migraciones para que la BD coincida con lo que CI y producción esperan.
- Cargar un dataset base pequeño y estable. Inserta solo lo que la app necesita para arrancar (roles, feature flags, unos pocos productos, una org).
- Crear credenciales locales útiles. Siembra un par de accesos conocidos (admin y usuario normal) y cualquier clave API falsa que la app espere, solo para uso local.
- Ejecutar una prueba rápida de humo. Golpea un endpoint, renderiza una página clave o ejecuta un archivo de test pequeño para que los fallos aparezcan inmediatamente.
Un patrón concreto es envolver todo esto en un script que los desarrolladores ejecuten desde la raíz del repo:
./dev/setup
Ese script puede imprimir lo que hizo y qué probar después, por ejemplo: “Login como admin: [email protected] / password123” y “Ejecuta: ./dev/smoke”. Mantén la salida corta y práctica.
Haz los scripts de onboarding amables para nuevos contribuyentes
Un buen script de incorporación se siente como una guía útil, no como un rompecabezas. Los nuevos contribuyentes deberían poder clonar el repo, ejecutar un comando y obtener una app funcional sin pedir pasos secretos.
Haz que la ruta de reset sea segura y obvia. Si alguien ejecuta un comando de reset, debe apuntar solo a recursos locales y decir qué va a borrar antes de hacerlo. Una comprobación de seguridad simple (o requerir la bandera --yes) evita accidentes.
Soporta variables de entorno, pero no hagas que la gente las busque. Proporciona valores por defecto sensatos para valores comunes como nombre de BD, puerto y email admin. Si tu app necesita realmente un valor real (como una clave API), falla rápido y di exactamente qué hacer.
Los pequeños detalles importan. Después de una ejecución exitosa, imprime un resumen corto: base de datos creada, migraciones aplicadas, usuarios seed añadidos y los datos exactos de acceso (email, contraseña, rol). Si tu app tiene varios servicios, muestra dónde está corriendo cada uno y qué comprobar si un puerto está ocupado.
Errores comunes que hacen perder horas
La mayor parte del dolor en la configuración local viene de atajos pequeños que parecen bien para una persona, pero colapsan cuando entra un segundo contribuyente.
Evita estas trampas comunes:
- Manipular la base de datos manualmente. Un edit SQL rápido o un volcado compartido se queda obsoleto y una instalación nueva no coincidirá con la máquina “funcional”.
- Datos seed aleatorios sin semilla fija. Si IDs, nombres de usuario o timestamps cambian en cada ejecución, acabarás con bugs y tests inestables.
- Copiar secretos reales o datos de producción. Pasar archivos
.env, hacer commit de claves o volcar filas de producción localmente crea problemas de seguridad y comportamiento confuso. - Fixtures que se desalinean del esquema. El setup “tiene éxito”, pero las páginas se bloquean después porque los campos de las fixtures no coinciden con las migraciones actuales.
- Setup que requiere clics por la UI. “Solo regístrate y crea un proyecto” suena simple hasta que son 15 clics en un orden frágil.
Un escenario común es que el login falle porque el seed creó contraseñas aleatorias mientras las fixtures aún referencian un campo antiguo del esquema. Alguien pasa una hora depurando auth cuando el verdadero problema es un setup inconsistente.
Comprobaciones rápidas antes de decir que es reproducible
Un setup solo es reproducible cuando alguien nuevo puede obtener el mismo resultado que tú sin leerte la mente.
La prueba de clonado en 5 minutos
Toma una máquina limpia (o una carpeta nueva) y actúa como si no supieras nada del proyecto. Tu objetivo es una app funcional desde cero.
Deberías poder confirmar, rápido:
- Una persona nueva puede ejecutar el setup sin adivinar pasos faltantes.
- La base de datos puede borrarse y reconstruirse desde cero en menos de 5 minutos en un portátil medio.
- Los scripts de seed pueden reejecutarse sin duplicados, claves foráneas rotas o fallos aleatorios.
- Siempre terminas con al menos un inicio de sesión funcional más un par de escenarios realistas (usuario admin, usuario normal y un estado “sin datos aún”).
- Los tests corren contra una base de datos limpia y separada (no contra la BD de dev).
Si algún elemento falla, anota el punto exacto de confusión y arregla eso primero.
Busca estado oculto
La mayoría de los problemas “me funciona en mi máquina” vienen de estado que vive fuera de tus scripts: un usuario creado manualmente, un archivo local con secretos, una migración única que nunca se ejecutó, o datos sobrantes de la semana pasada.
Una forma rápida de detectarlo es reconstruir dos veces seguidas: resetear la BD, ejecutar el setup, arrancar la app, luego resetear y hacerlo otra vez. La segunda ejecución debería verse idéntica a la primera.
Ejemplo concreto: si tu seed crea un usuario como [email protected], el script debería hacer upsert o recrearlo limpiamente cada vez, y la contraseña debería documentarse en un solo lugar.
Ejemplo: incorporar a un nuevo contribuyente en 30 minutos
Un nuevo contratista llega el lunes por la mañana. Tiene el repo, pero no contexto, sin BD existente y sin tiempo para crear cuentas y registros de muestra a mano. El objetivo es simple: que sea productivo el mismo día, con los mismos datos iniciales que los demás.
Siguen el README y ejecutan un comando, por ejemplo make dev-reset o npm run dev:reset. Ese comando elimina la base de datos local, la recrea, ejecuta migraciones, carga datos seed y instala un pequeño conjunto de fixtures. Al terminar, la app arranca con un inicio de sesión predecible.
El contratista inicia sesión usando una cuenta seed como [email protected] con una contraseña conocida. La cuenta ya está ligada a una organización, un workspace y dos proyectos. Un proyecto incluye relaciones “lo suficientemente reales”: varios usuarios, un par de roles, una factura pagada, un pago fallido y un ítem con comentarios. Las pantallas clave cargan inmediatamente sin configuración manual.
En 30 minutos pueden ejecutar el setup, iniciar sesión con éxito, abrir el dashboard y las vistas de detalle del proyecto, activar un caso límite conocido (como un banner de “pago fallido”) y reproducir un bug informado contra el mismo conjunto de fixtures que usan todos.
Cuando el esquema cambia, trata las fixtures como código, no como muestras desordenadas. Un hábito simple ayuda: actualiza migraciones primero, luego los scripts de seed (manteniendo IDs y timestamps estables), luego refresca fixtures y añade una comprobación rápida de humo (iniciar sesión, cargar las pantallas principales, golpear un endpoint).
Próximos pasos: mantenerlo estable a medida que la app crece
A medida que las funcionalidades se acumulan, el setup local suele ser lo primero que se degrada. La mejor defensa es mantener tu lista de “datos mínimos utilizables” corta y documentada. Piensa en el conjunto más pequeño de registros necesarios para usar la app de punta a punta: un usuario que pueda iniciar sesión, un workspace o proyecto, un par de ítems con aspecto real y los roles o ajustes que hacen que las pantallas principales funcionen.
Una vez que conozcas ese mínimo, protégelo con dos hábitos: un comando de reset que la gente realmente use y un pequeño conjunto de fixtures base que solo cambie cuando el producto realmente cambie.
Una rutina ligera de mantenimiento ayuda también: una vez al mes, reconstruye desde cero en una máquina limpia (o un contenedor/VM nueva) y cronométralo. Si tarda más de 10–15 minutos o requiere ediciones manuales, arréglalo de inmediato. Este es también un buen momento para escanear seeds en busca de secretos y confirmar que la autenticación funciona con una base de datos totalmente nueva.
Si estás lidiando con un codebase generado por IA que solo funciona en un portátil, generalmente no es solo “datos que faltan”. Es migraciones desalineadas, defaults de auth frágiles y scripts que solo tienen éxito una vez. FixMyMess (fixmymess.ai) se centra en diagnosticar y reparar problemas como este, especialmente para prototipos creados con herramientas como Lovable, Bolt, v0, Cursor y Replit, para que los equipos puedan reconstruir sus bases de datos locales y de test de forma predecible y avanzar con confianza.
Preguntas Frecuentes
What’s the difference between seed data and fixtures?
Los datos seed son la base pequeña que tu app necesita para resultar usable en desarrollo local, como roles, un par de usuarios y un espacio de trabajo de ejemplo.
Las fixtures son conjuntos específicos y conocidos de datos pensados para demostrar que un escenario se comporta igual cada vez, normalmente para tests o para reproducir un bug. Si estás navegando la UI manualmente, empieza por los datos seed; si verificas comportamientos automáticamente, confía en fixtures (o factories).
How much seed data should we include so the app isn’t empty?
Apunta al conjunto más pequeño que haga que los flujos principales funcionen de extremo a extremo. Unos pocos usuarios (admin y normal), una org/proyecto y una docena de registros por pantallas clave suelen ser suficientes.
Si añades demasiado, el setup se vuelve lento y la depuración ruidosa. Añade realismo donde afecte la lógica (roles, estados, permisos), no donde solo añada volumen (registros gigantes, montones de filas).
How do we prevent seed scripts from creating duplicate records?
Haz el script de seed idempotente. Es decir, volver a ejecutarlo debe producir el mismo estado final sin duplicados.
Usa upserts identificados por claves estables como email, slug o un UUID fijo que controles. Si quieres un estado completamente limpio, hazlo explícito con un paso de reset en lugar de insertar silenciosamente más filas en cada ejecución.
Should dev and test databases be separate?
Sepáralas y haz que sea difícil mezclarlas. La base de datos de desarrollo es para humanos que exploran funciones; la de tests debe reiniciarse con frecuencia y ejecutarse en aislamiento.
Si tus tests corren contra la BD de dev, fallarán aleatoriamente porque filas dejadas por un compañero o ejecuciones anteriores cambiaron el estado.
Why do people recommend deterministic IDs and timestamps in seeds and fixtures?
Los IDs y timestamps estables mantienen el comportamiento consistente entre máquinas y ejecuciones. Evitan problemas como cambios aleatorios en el orden, widgets de “actividad reciente” que se mueven, o tests de snapshot que fallan sin motivo.
No hace falta congelarlo todo, pero cualquier cosa de la que dependa la UI o los tests debe ser predecible.
What should a single “setup/reset” command do?
Un buen comando debería recrear la base de datos local, ejecutar migraciones, cargar datos seed y mostrar el siguiente paso (incluyendo cómo iniciar sesión).
Hazlo seguro para ejecutar repetidamente e incluye una salvaguarda fuerte para que no se ejecute contra entornos de producción (por ejemplo, comprobar NODE_ENV).
How do we handle optional services like Redis, S3, or email locally?
Trata los servicios opcionales como opcionales en el código, no solo en la documentación. La app debería arrancar sin Redis/S3/email si no son requeridos y debería mostrar un mensaje claro cuando una función no esté disponible.
Para desarrollo local, usa sustitutos seguros como almacenamiento en archivos o una bandeja de correo local para poder trabajar sin infraestructura adicional.
How do we avoid leaking secrets or using production data in local setup?
No siembres secretos reales, claves API ni datos de producción. Usa valores obviamente falsos para flujos locales y haz que la app falle rápido con un error claro cuando una clave real sea realmente necesaria.
Además, evita copiar archivos .env compartidos a la ligera; es así como las credenciales se filtran y el comportamiento se vuelve inconsistente entre máquinas.
Our app only runs on the original creator’s laptop. What’s the fastest fix?
Empieza reconstruyendo desde cero en una base de datos limpia y observa dónde falla. La mayoría de los problemas “solo funciona en un portátil” vienen de estado oculto: migraciones que faltan, filas creadas manualmente, valores por defecto de autenticación frágiles o scripts que solo tienen éxito la primera vez.
Si heredaste un proyecto generado por IA y el setup es un desastre, FixMyMess puede diagnosticar el codebase y hacer que la base de datos sea reconstruible para que cualquiera pueda ejecutar un reset predecible y obtener el mismo inicio de sesión funcional.
What do we do when fixtures or seed data drift from the schema after changes?
Primero, confirma que las migraciones son la única fuente de verdad para el esquema. Si alguien parcheó una tabla manualmente, captura ese cambio en una migración para que todas las máquinas coincidan.
Después, actualiza las fixtures para que coincidan con el esquema actual y mantenlas pequeñas. Una comprobación rápida después del setup (iniciar sesión, cargar una página clave, golpear un endpoint) te ayuda a detectar la deriva de inmediato.