Documento generado tras un análisis exhaustivo del frontend (Angular 19 + TailwindCSS) y del backend (Laravel × 2 APIs + Docker + MySQL). Las mejoras están ordenadas por prioridad (🔴 Crítica, 🟡 Media, 🟢 Baja) y por categoría.
- 🔴 Seguridad (Frontend)
- 🔴 Arquitectura y Calidad de Código
- 🟡 Gestión de Estado y Datos
- 🟡 UX / Diseño / Accesibilidad
- 🟡 Rendimiento
- 🟢 Testing (Frontend)
- 🟢 SEO y Meta Tags
- 🟢 Otras Mejoras (Frontend)
- 🔴 Seguridad (Backend)
- 🔴 Arquitectura Backend
- 🟡 Lógica de Negocio y Datos
- 🟡 Docker y Despliegue
- 🟢 Testing y Calidad (Backend)
Problema: Se usa any frecuentemente en vez de interfaces tipadas:
registerForm(): Observable<any>enauth.service.tslogin(): Observable<any>decoded: anyen múltiples archivoscreateReservation(reservation: any)enhouses.service.ts
Solución: Crear interfaces para todas las respuestas de API y los datos del JWT.
interface JwtPayload {
sub: number;
name: string;
email: string;
role: 'admin' | 'user';
exp: number;
iat: number;
}
interface LoginResponse {
token: string;
user: User;
}- Carpeta
Authcon A mayúscula (debería serauth). - Mezcla de español e inglés en nombres:
reservasUsuario,cerrarModal,mostrarFormularioAlquilervsshowRentalForm,loading,errorMessage. - Componente exportado como
UsersComponentpero la ruta esprofiley el archivo esprofile.component.ts.
Problema: La interfaz User tiene un campo password: string. Nunca debería existir el password del usuario en el frontend.
Solución: Eliminarlo de la interfaz y asegurarse de que el backend no lo envíe.
Problema: El usuario actual se obtiene de formas distintas en cada componente:
profile.component.ts: decodifica el token → llamagetProfile()→getUserById()house-detail.component.ts: decodifica el token → llamagetUserById()reservas.component.ts:getUserFromToken()directamente en el constructor
Solución: Centralizar la obtención del usuario actual en AuthService con un Signal<User | null> que se actualice al login y se limpie al logout.
Problema: Hay console.log() y console.error() esparcidos por toda la aplicación con mensajes de debug:
console.log('📤 Datos que se envían a la API:', ...)en house-formconsole.log('[Perfil] userId para reservas:', ...)en profileconsole.log('🏠 Casa eliminada correctamente')en admin house-cardconsole.log('[AuthService] Token recibido del backend:', ...)en auth.service
Solución: Implementar un LoggerService que solo loguee en desarrollo, o eliminar todos los logs manuales.
Problema: Cada servicio y componente maneja sus errores de forma local e inconsistente. Algunos silencian errores (handleError devuelve of(result)), otros los muestran con console.error().
Solución: Implementar un interceptor de errores global que:
- Muestre un toast/notificación al usuario
- Redirija a login en caso de 401
- Logee errores para debugging
Problema: En admin-dashboard.component.ts, adminId = 1 está hardcodeado. Debería obtenerse del token JWT del usuario autenticado.
Problema: La aplicación no tiene footer. Añadir un footer con información de contacto, links legales (privacidad, cookies, etc.) y branding daría un aspecto más completo y profesional.
Problema: La ruta wildcard { path: '**', redirectTo: 'auth/login' } redirige cualquier URL no encontrada al login. Sería mejor tener una página 404 personalizada que muestre un mensaje claro y un botón para volver al inicio.
Problema: Si la imagen de una casa no carga (URL rota, servidor caído), se muestra el icono de imagen rota del navegador. Solo house-detail tiene un fallback ("Sin imagen").
Solución: Añadir un (error) handler en todos los <img> o usar una directiva global:
<img [src]="imageUrl" (error)="img.src = '/images/placeholder-house.png'" #img />Problema: Cuando se cargan las casas o los datos del perfil, no hay indicación visual de carga (excepto en el dashboard con nb-spinner). El usuario ve una pantalla vacía.
Solución: Implementar skeleton loaders o spinners en:
- Lista de casas (home)
- Detalle de casa
- Perfil de usuario
- Historial de reservas
Problema: El formulario de reserva no valida que:
- La fecha de inicio sea posterior a hoy
- La fecha de fin sea posterior a la fecha de inicio
- El número de personas sea > 0
El usuario puede seleccionar fechas pasadas o poner la fecha de fin antes que la de inicio.
Problema: En reservas.component.html, la tabla tiene altura fija h-64 y no se adapta bien a móviles. Las columnas se comprimen y el contenido se desborda.
Solución: En móvil, cambiar a un layout de tarjetas en vez de tabla, o usar overflow-x-auto correctamente con scroll horizontal.
Problema: El submenú del header se abre con un clic y solo se cierra al hacer clic en un enlace o en el botón "Menú". Debería cerrarse también al hacer clic fuera del menú (click outside).
Solución: Añadir un @HostListener('document:click') o un overlay transparente.
Problema: El formulario de login no tiene:
- Enlace de "¿Olvidaste tu contraseña?"
- Toggle para mostrar/ocultar la contraseña
Problema: Al hacer clic en "Cerrar sesión", el logout se ejecuta inmediatamente sin confirmación. Un modal de confirmación evitaría cierres accidentales.
Problema: La página de Login usa bg-gradient-to-br from-blue-100 to-indigo-200 (tonos suaves), mientras que Register usa bg-gradient-to-br from-indigo-600 via-purple-600 to-pink-500 (tonos vibrantes). Los estilos de los inputs y botones también difieren. Deberían compartir el mismo design system.
Problema: La página de detalle de casa es muy simple: imagen + texto + mapa + botones. Se podría mejorar con:
- Galería de imágenes (carrusel si hay varias fotos)
- Rating y reviews de usuarios
- Amenidades/características con iconos (WiFi, piscina, parking, etc.)
- Precio por noche visible de forma prominente
- Calendario de disponibilidad
- Casas similares sugeridas
Problema: FontAwesome se carga de dos formas:
- Kit de FontAwesome vía
<script>enindex.html(CDN) - Paquetes
@fortawesome/*instalados vía npm
Esto duplica la carga del bundle. Elegir una sola estrategia.
Problema: Se importan muchos módulos de @nebular/theme en app.config.ts (NbLayoutModule, NbCardModule, NbSpinnerModule, NbSidebarModule, NbMenuModule, NbContextMenuModule, NbUserModule, NbSelectModule), pero solo se usan NbCardModule y NbSpinnerModule en el dashboard del admin. El resto añade peso al bundle sin usarse.
Solución: Eliminar los módulos no usados o migrar el dashboard a componentes propios con Tailwind.
Problema: echarts se importa completamente:
echarts: () => import('echarts')Esto trae todo el bundle de ECharts (~800KB min). Solo se usan gráficos de barras, líneas y pie.
Solución: Importar solo los módulos necesarios:
echarts: () => import('echarts/core').then(ec => {
// Registrar solo lo necesario
})Problema: import * as L from 'leaflet' se importa de forma estática en house-detail y house-form. Debería usarse lazy import ya que Leaflet es una librería pesada (~200KB).
Problema: Las imágenes de las casas se sirven directamente desde el backend sin ningún tipo de optimización (no hay lazy loading de imágenes, no hay formato webp, no hay thumbnails).
Solución:
- Añadir
loading="lazy"a todos los<img>que no estén en el viewport inicial - Considerar servir thumbnails para las tarjetas y full-size para el detalle
Problema: Existen archivos .spec.ts pero la mayoría son los generados automáticamente por Angular CLI sin tests reales. Ejemplos: app.component.spec.ts, login.component.spec.ts, house-list.component.spec.ts.
Solución: Implementar tests unitarios al menos para:
- Servicios:
AuthService,HouseService,ReservationService - Guards:
auth.guard.ts(especialmente dado el bug actual) - Componentes con lógica:
LoginComponent,RegisterComponent,HouseFormComponent - Pipes y directivas si se crean
Problema: No hay configuración de Cypress, Playwright, ni Protractor para tests end-to-end.
Solución: Configurar Playwright o Cypress para testear flujos críticos:
- Login → Ver casas → Detalle → Reservar
- Registro de usuario
- Admin: crear casa → ver en listado → editar → eliminar
Problema: El index.html solo tiene <title>RuralHouse</title>. Falta:
<meta name="description" content="...">- Open Graph tags para compartir en redes sociales
<meta name="robots" content="index, follow">
Problema: Todas las páginas muestran el mismo título "RuralHouse". Cada vista debería tener su propio título dinámico.
Solución: Usar Title service de Angular en cada componente o en un route resolver:
import { Title } from '@angular/platform-browser';
constructor(private title: Title) {
this.title.setTitle('Detalle de Casa - RuralControl');
}Problema: <html lang="en"> en index.html, pero toda la interfaz está en español. Debería ser lang="es".
Problema: El archivo Readme.md contiene marcadores de conflicto de Git (<<<<<<< HEAD, =======, >>>>>>> develop). Limpiar y unificar las dos versiones del contenido.
Problema: En package.json hay tailwindcss: ^3.4.17 como devDependency, pero también @tailwindcss/postcss: ^4.1.7 como dependency. Estas son versiones incompatibles (v3 vs v4). Se debe unificar.
Problema: Aunque ya se usa loadComponent para lazy loading de componentes, se podría agrupar todas las rutas admin bajo un lazy loaded module/routes para que los usuarios normales nunca descarguen el código del admin.
{
path: 'admin',
loadChildren: () => import('./pages/admin/admin.routes').then(m => m.ADMIN_ROUTES),
canActivate: [adminGuard],
}Problema: No existe un guard específico para rutas de administrador. Cualquier usuario autenticado puede acceder a /admin/* si conoce la URL.
Solución: Crear un adminGuard que verifique decoded.role === 'admin' y lo aplique a todas las rutas /admin/*.
Problema: El formulario de registro no muestra mensajes de error campo por campo (como sí lo hace el de login). El usuario no sabe qué campo tiene el error.
Solución: Añadir mensajes de validación debajo de cada campo:
@if (registerForm().get('email')?.dirty && registerForm().get('email')?.invalid) {
<div class="text-xs text-red-600">Email inválido</div>
}Problema: La app solo tiene tema claro. Un dark mode sería una mejora de UX muy valorada.
Problema: La URL base para las imágenes de las casas (http://92.112.127.238:8001/) está hardcodeada en múltiples templates HTML.
Solución: Crear una pipe o un servicio que construya la URL completa de la imagen:
@Pipe({ name: 'houseImage' })
export class HouseImagePipe implements PipeTransform {
transform(photoPath: string): string {
return `${environment.apiBaseUrlHouses}/${photoPath}`;
}
}Solución: Considerar añadir @angular/pwa para que la app funcione offline y se pueda instalar como aplicación nativa en móviles. Ideal para usuarios que buscan casas rurales en zonas con mala cobertura.
Problema: La app está íntegramente en español. Si se quiere expandir, se debería preparar para internacionalización usando @angular/localize.
Problema: Actualmente se usa SweetAlert2 para notificaciones, pero solo en algunos sitios. Sería mejor tener un sistema de toast notifications global y consistente (por ejemplo, con un servicio de notificaciones).
Problema CRÍTICO: Los archivos .env están incluidos en el repositorio con credenciales reales:
UserApi/.env:DB_USERNAME=zudex,DB_PASSWORD=Alameda005HouseApi/.env:DB_USERNAME=zudex,DB_PASSWORD=Alameda005docker-compose.yml:MYSQL_ROOT_PASSWORD: root,MYSQL_PASSWORD: 1234
Además, el .gitignore de UserApi no incluye .env (solo tiene 316 bytes, probablemente el default).
Solución:
- Añadir
.enval.gitignorede ambas APIs (si no está) - Eliminar los
.envdel historial de Git congit filter-branchoBFG Repo Cleaner - Rotar TODAS las contraseñas expuestas inmediatamente
- Usar solo
.env.examplecon valores placeholder en el repo
Problema: En UserApi/.env, la línea JWT_SECRET=tukey establece un secret de solo 5 caracteres. Esto hace que los tokens JWT sean triviales de falsificar por fuerza bruta.
Solución: Generar un secret seguro con php artisan jwt:secret (mínimo 32 caracteres aleatorios).
Problema: En HouseApi/routes/api.php, ninguna ruta tiene middleware de autenticación:
Route::post('houses', [HouseController::class, 'store']); // ❌ Cualquiera puede crear casas
Route::put('houses/{house}', [HouseController::class, 'update']); // ❌ Cualquiera puede editar
Route::delete('houses/{house}', [HouseController::class, 'destroy']); // ❌ Cualquiera puede eliminar
Route::post('reservations', [ReservationController::class, 'store']); // ❌ Cualquiera puede reservarSolución: Proteger las rutas de escritura con middleware de autenticación. Idealmente, la HouseApi debería verificar el JWT emitido por la UserApi, o implementar una comunicación inter-servicios segura.
Problema: En UserApi/routes/api.php, Route::get('/users/{id}', ...) es pública. Cualquier persona puede obtener la información de cualquier usuario solo conociendo su ID.
Solución: Proteger esta ruta o limitar los campos devueltos (solo nombre, no email ni datos sensibles).
Problema: Ambos archivos .env tienen APP_DEBUG=true. En producción, esto expone stack traces completos, variables de entorno y rutas internas al usuario en caso de error.
Solución: Crear archivos .env.production con APP_DEBUG=false y APP_ENV=production.
Problema: Ambas APIs usan la misma APP_KEY:
APP_KEY=base64:ppD2YueAqam3Qb09gtW4IkHmYEp3LfH+D2rBmIiANFo=
Cada aplicación Laravel debería tener su propia APP_KEY única.
Problema: Los endpoints auth/login y auth/register no tienen protección contra ataques de fuerza bruta. Un atacante puede intentar miles de contraseñas por minuto.
Solución: Añadir el middleware throttle:5,1 (5 intentos por minuto) a las rutas de login:
Route::middleware(['throttle:5,1'])->group(function () {
Route::post('auth/login', [AuthController::class, 'login']);
});Problema: El endpoint geocode/reverse en HouseController pasa los parámetros lat y lon directamente a una petición HTTP externa sin validar que sean números válidos.
Solución: Validar los parámetros:
$request->validate([
'lat' => 'required|numeric|between:-90,90',
'lon' => 'required|numeric|between:-180,180',
]);Problema: En HouseController::index(), los CORS headers se añaden manualmente en la respuesta:
return response()->json($houses)
->header('Access-Control-Allow-Origin', 'http://localhost:4200')
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
->header('Access-Control-Allow-Headers', 'Content-Type, Authorization');Esto es redundante y potencialmente conflictivo porque ya existe config/cors.php que maneja CORS globalmente. Además, esa cabecera manual solo permite localhost:4200, ignorando los otros origins del config.
Solución: Eliminar las cabeceras manuales y confiar en config/cors.php.
Problema: La HouseApi tiene su propio modelo User (HouseApi/app/Models/User.php) y su propia tabla users, pero la gestión de usuarios la hace la UserApi. Esto crea dos tablas de usuarios en la misma base de datos (houses_service) que nunca se sincroniza con la tabla real de usuarios.
Solución: Eliminar el modelo User y la migración de users de la HouseApi. Para validar usuarios, la HouseApi ya hace una petición HTTP a la UserApi (como en ReservationController::store()).
Problema: En ReservationController::store(), se hace una petición HTTP síncrona a la UserApi para verificar que el usuario existe:
$userApiUrl = "http://user-api:8000/api/users/{$validated['user_id']}";
$response = Http::get($userApiUrl);Si la UserApi está caída, la creación de reservas falla por completo. No hay timeout definido, no hay retry, no hay caché.
Solución:
- Añadir timeout:
Http::timeout(5)->get(...) - Considerar caché de verificación de usuario
- Manejar el error con un mensaje claro
Problema: El método update() tiene código comentado que indica funcionalidad incompleta:
// 'photo_path' => $photoPath, // Guardamos la nueva ruta de la imagen
// 'owner_id' => $validated['owner_id'] ?? $house->owner_id,La lógica de actualización de imagen está "medio implementada" (como dice el propio comentario en línea 105).
Solución: Completar la funcionalidad de actualización de imagen o eliminar el código muerto.
Problema: El Dockerfile de HouseApi dice EXPOSE 8000, pero en docker-compose.yml el servicio escucha en el puerto 8001. Esto puede causar confusión.
Solución: Cambiar a EXPOSE 8001 en el Dockerfile de HouseApi.
Problema: El modelo House no define la relación hasMany con Reservation, aunque Reservation sí tiene belongsTo(House::class). Esto impide usar $house->reservations en queries.
Solución:
// House.php
public function reservations()
{
return $this->hasMany(Reservation::class);
}Problema: Toda la validación se hace directamente en los métodos del controlador con $request->validate(). En Laravel, la práctica recomendada es usar Form Request classes para separar la lógica de validación.
Solución: Crear Form Requests como StoreHouseRequest, StoreReservationRequest, etc.
Problema: En AdminDashboardController::getAdminStats(), la línea 59 usa $averageRating:
'average_rating' => round($averageRating ?? 0, 2),Pero $averageRating nunca se define en el método. Esto causará un warning de PHP y siempre devolverá 0. Probablemente se eliminó la query pero no la referencia.
Solución: Implementar el cálculo del rating promedio o eliminar el campo de la respuesta.
Problema: En ReservationController::store(), la verificación de solapamiento de fechas es incorrecta:
$query->Where(function ($q) use ($validated) {
$q->where('start_date', '>=', $validated['start_date'])
->where('end_date', '<=', $validated['end_date']);
});Esta query solo detecta reservas completamente contenidas dentro del rango solicitado. No detecta reservas que:
- Empiezan antes y terminan dentro del rango
- Empiezan dentro y terminan después del rango
- Envuelven completamente el rango solicitado
Solución: Usar la lógica de solapamiento estándar:
$query->where('start_date', '<', $validated['end_date'])
->where('end_date', '>', $validated['start_date']);Problema: La migración define los status como ['pendiente', 'confirmado', 'cancelado'], pero en AdminDashboardController::getAdminStats() se busca status = 'confirmada' (con la 'a' final). Esto hará que nunca encuentre reservas confirmadas para esa métrica.
Problema: Al crear una casa (HouseController::store()), el owner_id se acepta sin verificar que exista como usuario administrador en la UserApi. Cualquiera podría asignar cualquier owner_id.
Problema: House::all() devuelve todas las casas sin paginación. Con muchas casas, esto afecta al rendimiento y consume memoria.
Solución: Implementar paginación:
public function index(Request $request)
{
return House::paginate($request->get('per_page', 20));
}Problema: El modelo House incluye average_rating en $fillable, y el factory genera un campo average_rating, pero la migración de la tabla houses no tiene esa columna. Esto da error al intentar guardar una casa con rating.
Problema: En HouseFactory.php, 'owner_id' => "1" está hardcodeado como string. Todas las casas de seed pertenecen al admin. Además, debería ser un integer, no un string.
Problema: version: '3.8' está deprecated en versiones actuales de Docker Compose. Se puede eliminar.
Problema: Los servicios user-api y house-api usan depends_on: - mysql, pero esto solo garantiza que el contenedor MySQL se inicie, no que esté listo para aceptar conexiones. El wait-for-it.sh mitiga esto parcialmente, pero un healthcheck nativo es más robusto.
Solución:
mysql:
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 5s
retries: 10
user-api:
depends_on:
mysql:
condition: service_healthyProblema: El command del docker-compose incluye php artisan db:seed --force, lo que ejecuta los seeders cada vez que se reinicia el contenedor. Esto creará registros duplicados (admin, casas de ejemplo) en cada restart.
Solución: Usar php artisan migrate --seed solo en el primer arranque, o añadir lógica de firstOrCreate en los seeders.
Problema: No hay archivo .dockerignore en ninguna de las APIs. Esto significa que node_modules/, vendor/, .git/, los .zip, etc., se copian al contexto de Docker, haciendo que las builds sean lentas e innecesariamente grandes.
Solución: Crear .dockerignore en ambas APIs:
vendor/
node_modules/
.git/
storage/logs/*
.env
*.zip
Problema: En HouseApi/.env, APP_URL=http://TuURL es un placeholder no configurado. Esto puede afectar a la generación de URLs de assets y links.
Problema: Ni la UserApi ni la HouseApi tienen tests más allá de los archivos de ejemplo que genera Laravel.
Solución: Implementar tests al menos para:
- AuthController: registro, login, logout, token expirado
- ReservationController: crear reserva, verificar solapamiento, validar usuario
- AdminDashboardController: estadísticas, reservas por mes
- HouseController: CRUD completo
Problema: No existe documentación de la API (ni Swagger/OpenAPI, ni Postman actualizado). El archivo Endpoints.postman_collection.json solo tiene 3.6KB, probablemente incompleto.
Solución: Usar un paquete como l5-swagger o scramble para generar documentación OpenAPI automáticamente.
Problema: Los errores en controladores se devuelven directamente al usuario con $e->getMessage(). No hay logging centralizado para monitorización.
Solución: Usar Log::error() en los catch blocks y configurar un canal de logging apropiado para producción.
| Categoría | Críticas 🔴 | Medias 🟡 | Bajas 🟢 |
|---|---|---|---|
| FRONTEND | |||
| Seguridad | 4 | — | — |
| Arquitectura | 7 | — | — |
| Estado y Datos | — | 4 | — |
| UX / Diseño | — | 11 | — |
| Rendimiento | — | 5 | — |
| Testing (FE) | — | — | 2 |
| SEO | — | — | 3 |
| Otras (FE) | — | — | 10 |
| BACKEND | |||
| Seguridad (BE) | 8 | — | — |
| Arquitectura (BE) | 7 | — | — |
| Lógica y Datos | — | 7 | — |
| Docker/Deploy | — | 5 | — |
| Testing (BE) | — | — | 3 |
| Total | 26 | 32 | 18 |
- Sacar
.envdel repositorio y rotar credenciales de BD - Cambiar
JWT_SECRETpor uno seguro (32+ caracteres) APP_DEBUG=falseen producción
- Proteger rutas de escritura de HouseApi con autenticación
- Rate limiting en login/register
- Corregir el bug del
authGuard(frontend) - Registrar el
AuthInterceptorenapp.config.ts - Centralizar URLs de API usando
environment.ts - Crear
AdminGuardpara proteger rutas admin (frontend) - Limpiar boilerplate de
app.component.html
- Corregir solapamiento de reservas en
ReservationController - Corregir
$averageRatingno definida en dashboard - Corregir status
'confirmada'vs'confirmado' - Eliminar CORS manuales de
HouseController::index() - Eliminar
any→ crear interfaces tipadas (frontend) - Corregir
registerFormconcomputed(frontend) - Centralizar manejo de errores HTTP
- Eliminar
console.logde producción - Corregir
lang="es"y añadir meta tags
- Añadir paginación al listado de casas (backend)
- Completar funcionalidad de update de imágenes (backend)
- Añadir relaciones Eloquent faltantes
- Añadir skeleton loaders (frontend)
- Mejorar responsive de tablas
- Validación de fechas en reservas
- Unificar estilo Login/Register
- Añadir footer y página 404
- Crear
.dockerignore - Añadir healthchecks a docker-compose
- Arreglar seeders para que no dupliquen datos
- Eliminar módulos Nebular no usados
- Optimizar imports de ECharts y Leaflet
- Lazy loading agrupado para admin
- Implementar tests unitarios (frontend + backend)
- Documentación API con Swagger/OpenAPI
- Form Request classes en Laravel
- Dark mode, PWA (opcionales)