CÓMO RESOLVER EL DESAFÍO DEL ESTACIONAMIENTO EN JAVASCRIPT
Introducción al Desafío del Estacionamiento
El desafío del estacionamiento es un ejercicio práctico que consiste en desarrollar una clase en JavaScript para simular la gestión de un estacionamiento virtual. Este desafío no solo permite practicar conceptos de programación orientada a objetos (OOP), sino que también ofrece la oportunidad de integrarlo con una interfaz gráfica usando React para visualizar su funcionamiento. En este tutorial, exploraremos cómo implementar esta clase y cómo construir una aplicación React que represente las operaciones del estacionamiento de manera interactiva. El objetivo es proporcionar una solución clara, funcional y visualmente atractiva, ideal para desarrolladores que buscan mejorar sus habilidades en JavaScript y React.
El desafío requiere crear una clase que gestione un estacionamiento con un número específico de plazas, permita estacionar y retirar vehículos, y proporcione información sobre el estado del estacionamiento. Además, integraremos esta lógica en una aplicación React para que los usuarios puedan interactuar con el sistema de manera dinámica. Este tutorial está diseñado para ser accesible tanto para programadores intermedios como para aquellos que desean profundizar en el desarrollo frontend con herramientas modernas.
A continuación, desglosaremos la implementación de la clase ParkingLot, explicaremos cada método y construiremos una aplicación React para visualizar las operaciones del estacionamiento. También abordaremos cómo optimizar el código y hacerlo más amigable para el usuario, incluyendo animaciones y un diseño responsivo.
Implementación de la Clase ParkingLot
La clase ParkingLot es el núcleo de la lógica del estacionamiento. Esta clase gestiona un conjunto de plazas de estacionamiento representadas como un arreglo, donde cada plaza puede estar ocupada por un vehículo (identificado por un ID) o vacía (representada por null). A continuación, presentamos el código completo de la clase y explicamos cada método en detalle.
class ParkingLot {
slots = [];
constructor(parkingSize) {
this.slots = new Array(parkingSize).fill(null);
}
park(carId) {
console.log(`Estacionando vehículo: ${carId}`);
if (this.slots.every((slot) => slot !== null)) {
return false;
}
for (let i = 0; i < this.slots.length; i++) {
if (this.slots[i] === null) {
this.slots[i] = carId;
return true;
}
}
}
remove(carId) {
console.log(`Retirando vehículo: ${carId}`);
if (this.slots.every((slot) => slot !== carId)) {
return false;
}
for (let i = 0; i < this.slots.length; i++) {
if (this.slots[i] === carId) {
this.slots[i] = null;
return true;
}
}
}
getSlots() {
console.log(`Plazas de estacionamiento: ${this.slots}`);
return this.slots;
}
getSize() {
console.log(`Tamaño del estacionamiento: ${this.slots.length}`);
return this.slots.length;
}
getAvailable() {
const availableSlots = this.slots.filter((s) => s === null).length;
console.log(`Plazas disponibles: ${availableSlots}`);
return availableSlots;
}
isFull() {
return this.getAvailable() === 0;
}
}
export default ParkingLot;
La clase comienza con una propiedad slots, que es un arreglo que representa las plazas del estacionamiento. El método constructor inicializa este arreglo con un tamaño definido por el parámetro parkingSize, llenándolo con valores null para indicar que todas las plazas están vacías inicialmente. Por ejemplo, si creamos una instancia con cinco plazas, el resultado sería:
[null, null, null, null, null]
Método park
El método park permite estacionar un vehículo identificado por un carId. Este método recorre el arreglo slots buscando una plaza vacía (es decir, null). Si encuentra una, asigna el carId a esa posición y retorna true. Si todas las plazas están ocupadas, retorna false. Este enfoque asegura que los vehículos se asignen de manera secuencial a las plazas disponibles, manteniendo la simplicidad en la gestión.
Método remove
El método remove elimina un vehículo del estacionamiento buscando su carId en el arreglo slots. Si lo encuentra, establece esa plaza como null y retorna true. Si el carId no está presente, retorna false. Este método es esencial para liberar plazas y permitir que nuevos vehículos puedan estacionarse.
Métodos getSlots, getSize, getAvailable e isFull
- getSlots: Retorna el arreglo
slots, permitiendo inspeccionar el estado actual del estacionamiento. - getSize: Devuelve el número total de plazas, útil para conocer la capacidad del estacionamiento.
- getAvailable: Calcula cuántas plazas están vacías filtrando los valores
nullen el arregloslots. Este método es clave para informar al usuario sobre la disponibilidad. - isFull: Verifica si el estacionamiento está completamente ocupado, retornando
truesi no hay plazas disponibles.
Estos métodos proporcionan una interfaz clara para interactuar con la clase y son especialmente útiles al integrarlos con una interfaz gráfica, como veremos más adelante.
Construcción de la Aplicación React
Ahora que tenemos la lógica del estacionamiento, podemos integrarla en una aplicación React para crear una experiencia interactiva. La aplicación permitirá a los usuarios seleccionar el tamaño del estacionamiento, estacionar y retirar vehículos, y visualizar el estado de las plazas en tiempo real. Para este tutorial, asumimos que la aplicación se construye con Vite, una herramienta moderna para proyectos frontend que ofrece un entorno de desarrollo rápido y eficiente.
Estructura del Proyecto
La estructura de la aplicación es sencilla y está organizada para mantener el código modular y fácil de mantener. A continuación, se muestra la estructura básica del directorio:
.
├── assets
│ └── car.png
├── src
│ ├── components
│ │ ├── ParkingSlot.jsx
│ │ └── Controls.jsx
│ ├── lib
│ │ └── ParkingLot.js
│ ├── pages
│ │ ├── Landing
│ │ │ └── Landing.jsx
│ │ └── Main
│ │ └── Main.jsx
│ ├── utils
│ │ └── generateLicensePlate.js
│ ├── App.jsx
│ ├── main.jsx
│ └── styles.css
- assets: Contiene recursos estáticos, como una imagen de un vehículo para la visualización.
- components: Incluye componentes React reutilizables, como
ParkingSlot(para representar cada plaza) yControls(para los botones de interacción). - lib: Almacena la clase
ParkingLotque implementamos anteriormente. - pages: Contiene las dos pantallas principales de la aplicación:
Landing(pantalla de bienvenida) yMain(pantalla de gestión del estacionamiento). - utils: Incluye funciones auxiliares, como una para generar matrículas de vehículos ficticias.
- App.jsx y main.jsx: Archivos principales para la lógica de enrutamiento y entrada de la aplicación.
Pantalla de Bienvenida (Landing)
La pantalla de bienvenida permite al usuario seleccionar el número de plazas para el estacionamiento (con un máximo de 20). Una vez seleccionado, el usuario es redirigido a la pantalla principal. El componente Landing.jsx podría verse así:
import { useState } from "react";
import { useNavigate } from "react-router-dom";
function Landing() {
const [size, setSize] = useState(5);
const navigate = useNavigate();
const handleSubmit = () => {
navigate("/main", { state: { parkingSize: size } });
};
return (
<div className="landing">
<h1>Bienvenido al Estacionamiento</h1>
<label>
Selecciona el tamaño del estacionamiento (máximo 20):
<input
type="number"
value={size}
onChange={(e) =>
setSize(Math.min(planter(e.target.value), 20))
}
min="1"
max="20"
/>
</label>
<button onClick={handleSubmit}>Iniciar</button>
</div>
);
}
export default Landing;
Este componente utiliza el estado de React (useState) para gestionar el tamaño seleccionado y useNavigate para redirigir al usuario a la pantalla principal con el tamaño elegido.
Pantalla Principal (Main)
La pantalla principal muestra las plazas del estacionamiento y permite al usuario estacionar o retirar vehículos. Cada plaza se representa con el componente ParkingSlot, que muestra si está vacía o ocupada por un vehículo con su respectiva matrícula. Un ejemplo simplificado de Main.jsx sería:
import { useState, useEffect } from "react";
import ParkingLot from "../lib/ParkingLot";
import ParkingSlot from "../components/ParkingSlot";
import { generateLicensePlate } from "../utils/generateLicensePlate";
function Main({ parkingSize }) {
const [parking, setParking] = useState(new ParkingLot(parkingSize));
const [slots, setSlots] = useState(parking.getSlots());
const handlePark = () => {
const carId = generateLicensePlate();
if (parking.park(carId)) {
setSlots([...parking.getSlots()]);
}
};
const handleRemove = (carId) => {
if (parking.remove(carId)) {
setSlots([...parking.getSlots()]);
}
};
return (
<div className="main">
<h1>Gestión del Estacionamiento</h1>
<button onClick={handlePark} disabled={parking.isFull()}>
Estacionar Vehículo
</button>
<div className="parking-lot">
{slots.map((slot, index) => (
<ParkingSlot
key={index}
carId={slot}
onRemove={() => handleRemove(slot)}
/>
))}
</div>
<p>Plazas disponibles: {parking.getAvailable()}</p>
</div>
);
}
export default Main;
Este componente inicializa una instancia de ParkingLot con el tamaño recibido, actualiza el estado de las plazas cuando se estaciona o retira un vehículo, y desactiva el botón de estacionar si el estacionamiento está lleno.
Visualización de Plazas (ParkingSlot)
El componente ParkingSlot representa una plaza individual y muestra si está ocupada o vacía. Un ejemplo de su implementación es:
function ParkingSlot({ carId, onRemove }) {
return (
<div className={`parking-slot ${carId ? "occupied" : "empty"}`}>
{carId ? (
<div onClick={() => onRemove(carId)}>
<img src="/assets/car.png" alt="Vehículo" />
<span>{carId}</span>
</div>
) : (
<span>Vacío</span>
)}
</div>
);
}
export default ParkingSlot;
Este componente utiliza una imagen para representar un vehículo ocupado y permite retirar el vehículo al hacer clic en la plaza.
Generación de Matrículas
Para hacer la visualización más realista, se utiliza una función auxiliar en generateLicensePlate.js para crear matrículas ficticias. Un ejemplo simple sería:
export function generateLicensePlate() {
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const numbers = "0123456789";
let plate = "";
for (let i = 0; i < 3; i++) {
plate += letters.charAt(Math.floor(Math.random() * letters.length));
}
plate += "-";
for (let i = 0; i < 4; i++) {
plate += numbers.charAt(Math.floor(Math.random() * numbers.length));
}
return plate;
}
Esta función genera matrículas en el formato ABC-1234, añadiendo un toque visual a la aplicación.
Estilización y Animaciones
Para mejorar la experiencia del usuario, la aplicación utiliza CSS modules para un estilo modular y responsivo. Por ejemplo, el archivo styles.css podría incluir:
.parking-lot {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 10px;
padding: 20px;
}
.parking-slot {
border: 2px solid #ccc;
padding: 10px;
text-align: center;
transition: all 0.3s ease;
}
.parking-slot.occupied {
background-color: #ffcccc;
}
.parking-slot.empty {
background-color: #ccffcc;
}
.parking-slot img {
width: 50px;
animation: slideIn 0.5s ease-in-out;
}
@keyframes slideIn {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
La animación slideIn simula el movimiento de un vehículo entrando a la plaza, haciendo la interacción más dinámica. El diseño en cuadrícula asegura que la aplicación sea responsiva y se adapte a diferentes tamaños de pantalla.
Optimizaciones y Mejoras
Para optimizar la aplicación, considera las siguientes mejoras:
-
Validación de Entrada: Asegúrate de que el tamaño del estacionamiento ingresado sea un número positivo y no exceda el máximo permitido.
-
Persistencia del Estado: Usa
localStoragepara guardar el estado del estacionamiento y restaurarlo al recargar la página. -
Manejo de Errores: Agrega notificaciones visuales (por ejemplo, con una librería como
react-toastify) para informar al usuario si no se puede estacionar o retirar un vehículo. -
Accesibilidad: Implementa atributos ARIA y soporte para teclado para mejorar la accesibilidad de la aplicación.
-
Pruebas Unitarias: Escribe pruebas para la clase
ParkingLotusando un marco como Jest para garantizar la robustez del código.
Un ejemplo de cómo implementar localStorage sería:
useEffect(() => {
const savedSlots = localStorage.getItem("parkingSlots");
if (savedSlots) {
parking.slots = JSON.parse(savedSlots);
setSlots([...parking.getSlots()]);
}
}, []);
useEffect(() => {
localStorage.setItem("parkingSlots", JSON.stringify(slots));
}, [slots]);
Esto asegura que el estado del estacionamiento persista entre sesiones.
Conclusiones
El desafío del estacionamiento es un excelente ejercicio para practicar conceptos de programación orientada a objetos en JavaScript y aprender a integrar lógica backend con una interfaz frontend en React. La clase ParkingLot proporciona una base sólida para gestionar un estacionamiento virtual, mientras que la aplicación React añade una capa visual que hace que la interacción sea intuitiva y atractiva. Al implementar animaciones y un diseño responsivo, la aplicación no solo es funcional, sino también agradable para el usuario.
Este proyecto también destaca la importancia de estructurar el código de manera modular y mantener una clara separación de responsabilidades entre la lógica de negocio (ParkingLot) y la interfaz de usuario (componentes React). Los desarrolladores pueden extender este proyecto agregando características como estadísticas en tiempo real, autenticación de usuarios o integración con una API para gestionar múltiples estacionamientos.
Con este tutorial, los programadores pueden adquirir habilidades prácticas en JavaScript, React y desarrollo frontend, mientras crean una aplicación que es tanto educativa como divertida. La combinación de lógica, visualización y experiencia del usuario hace que este desafío sea un excelente punto de partida para proyectos más complejos en el desarrollo web.