[English version below]
Aplicación web completa para reservar asientos de avión con las siguientes características:
- Autenticación: Registro/Login con validación de correos @gmail/@outlook
- Reservas:
- Modelo por Orden + Ítems (múltiples asientos por reserva)
- Selección manual (mapa interactivo) o aleatoria (servidor con locks)
- Validación de CUI (13 dígitos)
- Sistema VIP: descuento -10% para usuarios con >5 órdenes activas
- Gestión de reservas:
- Modificar asiento (+10% de recargo por modificación)
- Cancelar asiento individual o reserva completa
- Historial de modificaciones
- Importación/Exportación:
- XML con agrupación por usuario+fecha
- Procesamiento tolerante a errores con reporte detallado
- Reportes:
- Ocupación por clase (negocios/económica)
- Conteo de asientos libres/ocupados
- Historial de modificaciones y cancelaciones
- Notificaciones:
- Emails HTML con branding personalizado
- Templates para: registro, confirmación, modificación, cancelaciones
- Documentación: Swagger UI en
/api/docs
deffavia/
├── api/ # Backend Node.js + Express
│ ├── src/
│ │ ├── index.js # Servidor principal y configuración
│ │ ├── auth.js # Middleware JWT y rutas de autenticación
│ │ ├── users.js # CRUD de usuarios
│ │ ├── reservations.js # Lógica de reservas (manual/random)
│ │ ├── files.js # Export/Import XML
│ │ ├── mail.js # Envío de correos
│ │ ├── mail-templates.js # Templates HTML para emails
│ │ ├── reports.js # Reportes y estadísticas
│ │ └── openapi.yaml # Especificación OpenAPI 3.0
│ ├── docs/ # Documentación adicional
│ ├── .env.example # Variables de entorno template
│ └── package.json
│
├── airseat-web/ # Frontend Angular 20
│ ├── src/
│ │ ├── app/
│ │ │ ├── app/
│ │ │ │ ├── core/ # Servicios singleton
│ │ │ │ │ ├── api.service.ts
│ │ │ │ │ ├── auth.api.ts
│ │ │ │ │ ├── auth.interceptor.ts
│ │ │ │ │ ├── email.service.ts
│ │ │ │ │ ├── files-api.service.ts
│ │ │ │ │ └── reservations-api.service.ts
│ │ │ │ ├── features/ # Módulos por funcionalidad
│ │ │ │ │ ├── auth/
│ │ │ │ │ │ ├── auth.guard.ts
│ │ │ │ │ │ ├── login/
│ │ │ │ │ │ ├── register/
│ │ │ │ │ │ └── user-store.service.ts
│ │ │ │ │ ├── seats/
│ │ │ │ │ │ └── seat-map/ # Componente mapa de asientos
│ │ │ │ │ ├── reservations/
│ │ │ │ │ │ ├── reservation-wizard/ # Wizard multi-paso
│ │ │ │ │ │ ├── my-reservations/ # Gestión de reservas
│ │ │ │ │ │ ├── reservation-store.service.ts
│ │ │ │ │ │ └── components/
│ │ │ │ │ ├── files/
│ │ │ │ │ │ └── files-page/ # Import/Export XML
│ │ │ │ │ └── reports/
│ │ │ │ │ └── reports-page/ # Dashboard de reportes
│ │ │ │ ├── layout/ # Componentes de layout
│ │ │ │ │ └── topbar/
│ │ │ │ ├── pages/ # Páginas estáticas
│ │ │ │ │ ├── home/
│ │ │ │ │ └── about/
│ │ │ │ └── shared/ # Utilidades compartidas
│ │ │ │ ├── models/
│ │ │ │ └── utils/
│ │ │ ├── app.component.ts
│ │ │ ├── app.routes.ts
│ │ │ └── app.config.ts
│ │ ├── environments/
│ │ │ └── environment.ts # Configuración del entorno
│ │ ├── styles.scss # Estilos globales (tema claro)
│ │ └── index.html
│ ├── proxy.conf.json # Proxy para desarrollo
│ ├── angular.json
│ └── package.json
│
├── db/ # Scripts SQL
│ ├── schema_aligned.sql # Esquema completo de la BD
│ ├── migrations/
│ │ └── 20251020_create_views_for_reports.sql
│ └── sanity_check.sql # Verificación de integridad
│
├── docs/
│ └── Deffavia.postman_collection.json
│
└── README.md
- Node.js: 18+ (recomendado 20+)
- PostgreSQL: 14+ (recomendado 16)
- SMTP: Servidor real o usa Ethereal para pruebas automáticas
- Angular CLI: 20+ (opcional, usa
npxsi no está instalado globalmente)
cd api
cp .env.example .envEdita api/.env con tus credenciales:
# Base de datos
PGHOST=localhost
PGPORT=5432
PGDATABASE=deffavia
PGUSER=postgres
PGPASSWORD=tu_password
# JWT
JWT_SECRET=tu_secreto_super_seguro_de_al_menos_32_caracteres
# SMTP (opcional, usa Ethereal si no tienes)
USE_ETHEREAL=true
# O configura SMTP real:
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=[email protected]
SMTP_PASS=tu_app_password
SMTP_FROM=Deffavia <[email protected]>
# Puerto del servidor
PORT=3001# Crear la base de datos
psql -U postgres -c "CREATE DATABASE deffavia;"
# Aplicar el esquema principal
psql -U postgres -d deffavia -f db/schema_aligned.sql
# Aplicar migraciones (vistas para reportes)
psql -U postgres -d deffavia -f db/migrations/20251020_create_views_for_reports.sql
# Verificar integridad (opcional)
psql -U postgres -d deffavia -f db/sanity_check.sqlcd api
npm install
npm startLa API estará disponible en:
- API: http://localhost:3001
- Swagger UI: http://localhost:3001/api/docs
Crea los 57 asientos predefinidos (12 negocios + 45 económicos):
curl -X POST http://localhost:3001/api/admin/seed-seatsRespuesta esperada:
{
"ok": true,
"insertedOrExisting": 57
}cd airseat-web
npm installEl archivo proxy.conf.json redirige /api al backend:
{
"/api": {
"target": "http://localhost:3001",
"secure": false,
"changeOrigin": true
}
}Edita src/environments/environment.ts si necesitas cambiar configuraciones:
export const environment = {
production: false,
apiBaseUrl: '/api',
mailEndpoint: '/api/mail/send-quote',
priceBusiness: 1200, // Precio clase negocios (Q)
priceEconomy: 600, // Precio clase económica (Q)
vipThreshold: 5, // Órdenes activas para VIP
modificationIncrease: 0.1, // 10% recargo por modificación
};npm startLa aplicación estará disponible en: http://localhost:4200
Usando la interfaz web (http://localhost:4200)
-
Registro:
- Ve a "Registro" en la barra de navegación
- Usa un email @gmail.com o @outlook.com
- Recibirás un correo de bienvenida
-
Login:
- Inicia sesión con las credenciales creadas
- El sistema guarda el token JWT en localStorage
-
Crear reserva:
- Ve a "Reservar" → Wizard multi-paso
- Selecciona cantidad, clase y modo (manual/aleatorio)
- Completa datos de pasajeros (nombre, CUI, maleta)
- Revisa el resumen y correo de confirmación
-
Mis reservas:
- Ve a "Mis reservas"
- Modifica asientos (aplica +10% de recargo)
- Cancela asientos individuales o reserva completa
-
Exportar/Importar:
- Ve a "Archivos"
- Exporta reservas activas a XML
- Importa XML con manejo de errores
-
Reportes:
- Ve a "Reportes"
- Consulta ocupación, conteos y modificaciones
Importa docs/Deffavia.postman_collection.json:
Variables de colección:
{
"baseUrl": "http://localhost:3001",
"token": "",
"userEmail": ""
}Flujo de prueba:
POST /api/users→ Registro (guarda email en variable)POST /api/auth/login→ Login (guarda token en variable)POST /api/reservations→ Crear reserva (manual o random)GET /api/reservations/my→ Ver mis reservasPATCH /api/reservations/:orderId/items/:itemId/seat→ Modificar asiento (+10%)POST /api/reservations/:orderId/items/:itemId/cancel→ Cancelar asientoPOST /api/reservations/:orderId/cancel→ Cancelar reserva completaGET /api/files/export-xml→ Exportar XMLPOST /api/files/import-xml→ Importar XMLGET /api/reports/summary→ Reporte de ocupación
seat (asientos)
id SERIAL PRIMARY KEY
code VARCHAR(4) UNIQUE -- Ej: "A1", "I7"
class VARCHAR(20) -- 'business' | 'economy'reservation_order (órdenes de reserva)
id SERIAL PRIMARY KEY
user_id INTEGER -- FK a users (opcional)
user_email VARCHAR(255)
mode VARCHAR(20) -- 'manual' | 'random'
status VARCHAR(20) -- 'active' | 'cancelled'
price_subtotal DECIMAL(10,2)
discount_total DECIMAL(10,2)
modifiers_total DECIMAL(10,2)
total DECIMAL(10,2)
reserved_at TIMESTAMP DEFAULT NOW()reservation_item (ítems individuales por asiento)
id SERIAL PRIMARY KEY
order_id INTEGER NOT NULL -- FK a reservation_order
seat_id INTEGER NOT NULL -- FK a seat
passenger_name VARCHAR(255)
cui VARCHAR(13)
has_luggage BOOLEAN
price DECIMAL(10,2)
modifiers DECIMAL(10,2)
discount DECIMAL(10,2)
total DECIMAL(10,2)
status VARCHAR(20) -- 'active' | 'cancelled'
modified_count INTEGER DEFAULT 0
created_at TIMESTAMP DEFAULT NOW()- Único parcial:
ux_reservation_item_seat_active- Previene doble ocupación: solo un ítem activo por asiento
- v_seat_occupancy: Estado actual de cada asiento (libre/ocupado)
- v_seat_class_counts: Conteos por clase de asiento
- v_order_items: Join completo de órdenes e ítems para reportes
-
JWT Bearer Authentication:
- Token generado en
/api/auth/login - Middleware verifica token en rutas protegidas
- Expiración configurable
- Token generado en
-
Validación de CUI:
- Algoritmo de verificación de dígito verificador
- Validado en registro y modificaciones
-
Locks de base de datos:
SELECT ... FOR UPDATE SKIP LOCKEDen asignación aleatoria- Previene condiciones de carrera
-
Validación de email:
- Solo dominios @gmail.com y @outlook.com permitidos
- Verificación en registro
-
Auth Guard:
- Protege rutas que requieren autenticación
- Redirige a login si no hay token
-
HTTP Interceptor:
- Inyecta token JWT automáticamente
- Maneja errores 401 (token expirado)
-
Validación en formularios:
- Validators de Angular Reactive Forms
- Validación de CUI en tiempo real
<?xml version="1.0" encoding="UTF-8"?>
<flightReservation>
<flightSeat>
<seatNumber>A3</seatNumber>
<passengerName>Juan Pérez</passengerName>
<user>[email protected]</user>
<idNumber>1234567890123</idNumber>
<hasLuggage>true</hasLuggage>
<reservationDate>15/01/2025 14:30</reservationDate>
</flightSeat>
<!-- más asientos -->
</flightReservation>- Agrupa por
user + reservationDate→ crea una orden por grupo - Continúa ante errores (asiento ocupado, CUI inválido, etc.)
- Retorna reporte detallado con éxitos/errores
Los emails HTML incluyen:
- Gradiente morado-rosado en header
- Tablas responsive para detalles
- Botones de acción estilizados
- Footer con información de contacto
- Bienvenida (
buildUserCreatedHtml) - Confirmación de reserva (
buildOrderCreatedHtml) - Modificación de asiento (
buildItemModifiedHtml) - Cancelación de asiento (
buildItemCanceledHtml) - Cancelación de reserva (
buildOrderCanceledHtml)
Con USE_ETHEREAL=true, cada email muestra en consola:
✉ Email enviado a [email protected]
🔗 Ver en: https://ethereal.email/message/[id]
Error: relation "v_seat_class_counts" does not exist
psql -U postgres -d deffavia -f db/migrations/20251020_create_views_for_reports.sqlError: ON CONSTRAINT ... DO UPDATE falla
- Verifica que el índice único parcial existe
- Usa
ON CONFLICT DO NOTHINGen inserts manuales
Asiento ocupado en modo random
- Confirma que
/api/seatsusav_seat_occupancy - Verifica locks con
SELECT ... FOR UPDATE SKIP LOCKED
Emails no se envían
- Revisa configuración SMTP en
.env - Usa
USE_ETHEREAL=truepara testing sin SMTP real - Verifica logs de consola para enlaces de previsualización
Error: Cannot GET /api/...
- Verifica que el backend está corriendo en :3001
- Revisa
proxy.conf.json - Reinicia
ng serve
Token expirado
- El token JWT expira (configurable en backend)
- Logout y vuelve a hacer login
- Verifica que
auth.interceptor.tsestá activo
Mapa de asientos no carga
- Verifica que
/api/seatsresponde - Abre DevTools → Network para ver errores
- Confirma que
seed-seatsse ejecutó
CUI inválido
- Debe ser exactamente 13 dígitos
- El dígito verificador debe ser correcto
- Usa generador de CUI válido para pruebas
cd api
npm test # Si tienes tests configuradoscd airseat-web
# Unit tests
npm test
# E2E tests
npm run e2e
# Cobertura
npm test -- --code-coveragecd api
heroku create deffavia-api
heroku addons:create heroku-postgresql:mini
heroku config:set JWT_SECRET=tu_secreto_produccion
heroku config:set USE_ETHEREAL=false
heroku config:set SMTP_HOST=...
git push heroku maincd airseat-web
npm run build
vercel --prodConfigura en Vercel:
- Build Command:
npm run build - Output Directory:
dist/airseat-web/browser
ISC
Complete flight seat reservation system with:
- Authentication: Register/Login with @gmail/@outlook validation
- Reservations:
- Order + Items model (multiple seats per booking)
- Manual (interactive map) or random selection (server-side with locks)
- CUI validation (13 digits)
- VIP system: -10% discount for users with >5 active orders
- Reservation management:
- Modify seat (+10% surcharge)
- Cancel individual seat or complete order
- Modification history
- Import/Export:
- XML with grouping by user+date
- Error-tolerant processing with detailed report
- Reports:
- Occupancy by class (business/economy)
- Free/occupied seat counts
- Modification and cancellation history
- Notifications:
- Branded HTML emails
- Templates for: welcome, confirmation, modification, cancellations
- Documentation: Swagger UI at
/api/docs
cd api
cp .env.example .env
# Edit .env with your credentials
npm install
npm start
# API: http://localhost:3001
# Swagger: http://localhost:3001/api/docsCreate database:
psql -U postgres -c "CREATE DATABASE deffavia;"
psql -U postgres -d deffavia -f db/schema_aligned.sql
psql -U postgres -d deffavia -f db/migrations/20251020_create_views_for_reports.sqlSeed seats:
curl -X POST http://localhost:3001/api/admin/seed-seatscd airseat-web
npm install
npm start
# App: http://localhost:4200Use Swagger UI at /api/docs or import Postman collection from docs/Deffavia.postman_collection.json
{
"baseUrl": "http://localhost:3001",
"token": "",
"userEmail": ""
}- POST
/api/users→ Register - POST
/api/auth/login→ Get JWT token - POST
/api/reservations→ Create reservation (manual/random) - GET
/api/reservations/my→ View my reservations - PATCH
/api/reservations/:orderId/items/:itemId/seat→ Modify seat (+10%) - POST
/api/reservations/:orderId/items/:itemId/cancel→ Cancel seat - POST
/api/reservations/:orderId/cancel→ Cancel order - GET
/api/files/export-xml→ Export to XML - POST
/api/files/import-xml→ Import from XML - GET
/api/reports/summary→ Occupancy report
- 57 seats: 12 business + 45 economy
- VIP discount: -10% for users with >5 active orders
- Seat modification: +10% surcharge per change
- XML export/import with error handling
- Real-time seat map with visual feedback
- Responsive design (mobile-friendly)
- Email notifications with HTML templates
- Comprehensive reporting
- Backend: Node.js 20, Express 4, PostgreSQL 16
- Frontend: Angular 20, Standalone Components, Signals
- Auth: JWT Bearer tokens
- Email: Nodemailer (SMTP/Ethereal)
- Docs: OpenAPI 3.0 (Swagger UI)
ISC