""" seed_data.py — Datos de demo completos para Fleet Management Ejecutar: python seed_data.py Cuentas creadas: Admin: admin@fleet.com / admin123 Owner 1: rmitchell@email.com / owner123 (Robert Mitchell - 2 botes) Owner 2: msantos@email.com / owner123 (Maria Santos - 2 botes) Owner 3: cvega@email.com / owner123 (Carlos Vega - 1 bote) Owner 4: jwilliams@email.com / owner123 (Jennifer Williams - 1 bote) """ from app import create_app, db from app.models import Company, User, Vessel, Captain, Charter, WorkOrder, Voucher, AccountingEntry, FuelEntry from werkzeug.security import generate_password_hash from datetime import date from datetime import datetime, timedelta import random app = create_app() def get_or_create(model, defaults=None, **kwargs): instance = model.query.filter_by(**kwargs).first() if instance: return instance, False params = {**kwargs, **(defaults or {})} instance = model(**params) db.session.add(instance) db.session.flush() return instance, True with app.app_context(): print("=" * 55) print(" Fleet Management — Cargando datos de demo") print("=" * 55) # ============================================================ # MANAGEMENT COMPANY + ADMIN USER # ============================================================ mgmt, _ = get_or_create(Company, defaults={'type': 'management', 'phone': '305-900-0001'}, name='Al & Al Management LLC', email='admin@fleet.com') admin_user, created = get_or_create(User, defaults={ 'name': 'Admin Fleet', 'password_hash': generate_password_hash('admin123'), 'company_id': mgmt.id, 'role': 'admin', 'is_active': True, 'is_super_admin': True }, email='admin@fleet.com') # Ensure existing admin is marked as super_admin if not admin_user.is_super_admin: admin_user.is_super_admin = True if created: print(" [+] Admin creado: admin@fleet.com / admin123") else: print(" [=] Admin ya existe") db.session.commit() # ============================================================ # OWNERS (companies + portal users) # ============================================================ owners_data = [ { 'name': 'Robert Mitchell', 'email': 'rmitchell@email.com', 'phone': '305-555-2001', }, { 'name': 'Maria Santos', 'email': 'msantos@email.com', 'phone': '305-555-2002', }, { 'name': 'Carlos Vega', 'email': 'cvega@email.com', 'phone': '786-555-2003', }, { 'name': 'Jennifer Williams', 'email': 'jwilliams@email.com', 'phone': '786-555-2004', }, ] owner_companies = {} for od in owners_data: company, created = get_or_create(Company, defaults={'type': 'owner', 'phone': od['phone']}, name=od['name'], email=od['email']) owner_companies[od['name']] = company user, ucreated = get_or_create(User, defaults={ 'name': od['name'], 'password_hash': generate_password_hash('owner123'), 'company_id': company.id, 'role': 'owner', 'is_active': True }, email=od['email']) if ucreated: print(f" [+] Owner: {od['email']} / owner123") else: print(f" [=] Owner ya existe: {od['email']}") db.session.commit() # ============================================================ # CAPTAINS # ============================================================ captains_data = [ {'name': 'Carlos Pérez', 'phone': '305-555-5001', 'license_number': 'USCG-REC-12345', 'hourly_rate': 55}, {'name': 'Miguel Torres', 'phone': '305-555-5002', 'license_number': 'USCG-REC-67890', 'hourly_rate': 60}, {'name': 'Roberto Díaz', 'phone': '786-555-5003', 'license_number': 'USCG-REC-11223', 'hourly_rate': 50}, ] captains = {} for cd in captains_data: cap, created = get_or_create(Captain, defaults={ 'phone': cd['phone'], 'license_number': cd['license_number'], 'hourly_rate': cd['hourly_rate'], 'company_id': mgmt.id }, name=cd['name']) captains[cd['name']] = cap if created: print(f" [+] Capitán: {cd['name']}") db.session.commit() # ============================================================ # VESSELS (6 embarcaciones) # ============================================================ # plan_id: 1=Básico $199, 2=Estándar $399, 3=Mantenimiento $299, 4=Plus $599 vessels_data = [ { 'name': 'Blue Horizon', 'make': 'Sea Ray', 'model': '430 Sundancer', 'engines': '2 x Mercruiser 496 MAG', 'length': 43, 'fuel_consumption_14knots': 65, 'base_rate_4h': 1200, 'hourly_rate_extra': 300, 'charter_percentage': 25, 'plan_id': 4, 'owner': 'Robert Mitchell', 'hin': 'SRAY43001A222' }, { 'name': 'Sol y Mar', 'make': 'Azimut', 'model': '38 Flybridge', 'engines': '2 x Volvo Penta D6-370', 'length': 38, 'fuel_consumption_14knots': 55, 'base_rate_4h': 950, 'hourly_rate_extra': 250, 'charter_percentage': 25, 'plan_id': 2, 'owner': 'Robert Mitchell', 'hin': 'AZMT38002B222' }, { 'name': 'La Perla', 'make': 'Contender', 'model': '32 ST', 'engines': '2 x Yamaha F350', 'length': 32, 'fuel_consumption_14knots': 42, 'base_rate_4h': 750, 'hourly_rate_extra': 190, 'charter_percentage': 25, 'plan_id': 2, 'owner': 'Maria Santos', 'hin': 'CONT32003C222' }, { 'name': 'Brisa Marina', 'make': 'Grady-White', 'model': 'Freedom 285', 'engines': '2 x Yamaha F150', 'length': 28, 'fuel_consumption_14knots': 30, 'base_rate_4h': 550, 'hourly_rate_extra': 140, 'charter_percentage': 25, 'plan_id': 1, 'owner': 'Maria Santos', 'hin': 'GRDY28004D222' }, { 'name': 'Veloce', 'make': 'Scarab', 'model': '35 Sport', 'engines': '2 x Mercury Verado 400R', 'length': 35, 'fuel_consumption_14knots': 50, 'base_rate_4h': 850, 'hourly_rate_extra': 220, 'charter_percentage': 25, 'plan_id': 1, 'owner': 'Carlos Vega', 'hin': 'SCRB35005E222' }, { 'name': 'Lady J', 'make': 'Sea Ray', 'model': '480 Sundancer', 'engines': '2 x Zeus Pod 600', 'length': 48, 'fuel_consumption_14knots': 75, 'base_rate_4h': 1500, 'hourly_rate_extra': 375, 'charter_percentage': 25, 'plan_id': 4, 'owner': 'Jennifer Williams', 'hin': 'SRAY48006F222' }, ] vessels = {} for vd in vessels_data: owner_co = owner_companies[vd['owner']] vessel, created = get_or_create(Vessel, defaults={ 'make': vd['make'], 'model': vd['model'], 'engines': vd['engines'], 'length': vd['length'], 'fuel_consumption_14knots': vd['fuel_consumption_14knots'], 'base_rate_4h': vd['base_rate_4h'], 'hourly_rate_extra': vd['hourly_rate_extra'], 'charter_percentage': vd['charter_percentage'], 'plan_id': vd['plan_id'], 'owner_company_id': owner_co.id, 'management_company_id': mgmt.id, 'hin': vd['hin'], 'is_active': True }, name=vd['name']) vessels[vd['name']] = vessel if created: print(f" [+] Bote: {vd['name']} ({vd['make']} {vd['model']}) — {vd['owner']}") db.session.commit() # ============================================================ # CHARTERS # ============================================================ # Clientes reales con datos charterers = [ {'name': 'Andrew Lawson', 'phone': '305-600-1001', 'email': 'alawson@gmail.com'}, {'name': 'Sophia Chen', 'phone': '305-600-1002', 'email': 'sophia.chen@mail.com'}, {'name': 'Marcus Johnson', 'phone': '786-600-1003', 'email': 'mjohnson@corp.com'}, {'name': 'Isabella Gomez', 'phone': '305-600-1004', 'email': 'igomez@gmail.com'}, {'name': 'David Park', 'phone': '786-600-1005', 'email': 'dpark@hotmail.com'}, {'name': 'Nicole Fontaine', 'phone': '305-600-1006', 'email': 'nfontaine@gmail.com'}, {'name': 'Tyler Brooks', 'phone': '786-600-1007', 'email': 'tbrooks@corp.com'}, {'name': 'Valentina Cruz', 'phone': '305-600-1008', 'email': 'vcruz@gmail.com'}, {'name': 'James Whitfield', 'phone': '305-600-1009', 'email': 'jwhitfield@mail.com'}, {'name': 'Camila Restrepo', 'phone': '786-600-1010', 'email': 'crestrepo@gmail.com'}, ] def calc_charter(vessel, hours): base = vessel.base_rate_4h extra = vessel.hourly_rate_extra total = base if hours <= 4 else base + (hours - 4) * extra pct = vessel.charter_percentage mgmt_earn = round(total * pct / 100, 2) owner_earn = round(total - mgmt_earn, 2) return round(total, 2), mgmt_earn, owner_earn now = datetime.utcnow() charters_def = [ # Blue Horizon — 4 charters completados, 1 próximo {'vessel': 'Blue Horizon', 'days_ago': 45, 'hours': 4, 'charterer': 0, 'status': 'completed'}, {'vessel': 'Blue Horizon', 'days_ago': 32, 'hours': 6, 'charterer': 3, 'status': 'completed'}, {'vessel': 'Blue Horizon', 'days_ago': 18, 'hours': 8, 'charterer': 6, 'status': 'completed'}, {'vessel': 'Blue Horizon', 'days_ago': 7, 'hours': 4, 'charterer': 9, 'status': 'completed'}, {'vessel': 'Blue Horizon', 'days_ago': -5, 'hours': 6, 'charterer': 1, 'status': 'signed'}, # Sol y Mar — 3 completados {'vessel': 'Sol y Mar', 'days_ago': 38, 'hours': 5, 'charterer': 2, 'status': 'completed'}, {'vessel': 'Sol y Mar', 'days_ago': 21, 'hours': 4, 'charterer': 5, 'status': 'completed'}, {'vessel': 'Sol y Mar', 'days_ago': 10, 'hours': 6, 'charterer': 8, 'status': 'completed'}, # La Perla — 3 completados, 1 draft {'vessel': 'La Perla', 'days_ago': 50, 'hours': 4, 'charterer': 1, 'status': 'completed'}, {'vessel': 'La Perla', 'days_ago': 29, 'hours': 4, 'charterer': 4, 'status': 'completed'}, {'vessel': 'La Perla', 'days_ago': 14, 'hours': 5, 'charterer': 7, 'status': 'completed'}, {'vessel': 'La Perla', 'days_ago': -3, 'hours': 4, 'charterer': 0, 'status': 'draft'}, # Brisa Marina — 2 completados {'vessel': 'Brisa Marina', 'days_ago': 42, 'hours': 4, 'charterer': 3, 'status': 'completed'}, {'vessel': 'Brisa Marina', 'days_ago': 15, 'hours': 4, 'charterer': 6, 'status': 'completed'}, # Veloce — 3 completados, 1 signed {'vessel': 'Veloce', 'days_ago': 55, 'hours': 4, 'charterer': 5, 'status': 'completed'}, {'vessel': 'Veloce', 'days_ago': 35, 'hours': 6, 'charterer': 2, 'status': 'completed'}, {'vessel': 'Veloce', 'days_ago': 12, 'hours': 4, 'charterer': 9, 'status': 'completed'}, {'vessel': 'Veloce', 'days_ago': -7, 'hours': 5, 'charterer': 4, 'status': 'signed'}, # Lady J — 4 completados, 1 próximo (el más caro) {'vessel': 'Lady J', 'days_ago': 60, 'hours': 8, 'charterer': 7, 'status': 'completed'}, {'vessel': 'Lady J', 'days_ago': 40, 'hours': 6, 'charterer': 0, 'status': 'completed'}, {'vessel': 'Lady J', 'days_ago': 22, 'hours': 4, 'charterer': 3, 'status': 'completed'}, {'vessel': 'Lady J', 'days_ago': 8, 'hours': 10, 'charterer': 8, 'status': 'completed'}, {'vessel': 'Lady J', 'days_ago': -10,'hours': 6, 'charterer': 1, 'status': 'signed'}, ] charters_created = 0 vouchers_created = 0 for cd in charters_def: vessel = vessels[cd['vessel']] ch_data = charterers[cd['charterer']] start_dt = now - timedelta(days=cd['days_ago'], hours=10) total, mgmt_earn, owner_earn = calc_charter(vessel, cd['hours']) existing = Charter.query.filter_by( vessel_id=vessel.id, charterer_name=ch_data['name'], hours=cd['hours'] ).filter( Charter.start_datetime >= start_dt - timedelta(hours=2), Charter.start_datetime <= start_dt + timedelta(hours=2) ).first() if existing: continue charter = Charter( vessel_id=vessel.id, charterer_name=ch_data['name'], charterer_phone=ch_data['phone'], charterer_email=ch_data['email'], start_datetime=start_dt, hours=cd['hours'], total_base_rate=total, management_percentage=vessel.charter_percentage, management_earnings=mgmt_earn, owner_earnings=owner_earn, status=cd['status'], completed_at=start_dt + timedelta(hours=cd['hours']) if cd['status'] == 'completed' else None ) db.session.add(charter) db.session.flush() charters_created += 1 # Generar voucher para charters completados if cd['status'] == 'completed': fuel = round(vessel.fuel_consumption_14knots * cd['hours'] * random.uniform(0.8, 1.0)) tip_pct = random.choice([15, 18, 20]) voucher = Voucher( charter_id=charter.id, total_charged=total, fuel_actual_liters=fuel, deviation_charged=0, tip_amount=round(total * tip_pct / 100, 2), tip_percentage=tip_pct, issued_at=charter.completed_at, paid_at=charter.completed_at + timedelta(days=1) ) db.session.add(voucher) vouchers_created += 1 db.session.commit() print(f"\n [+] Charters creados: {charters_created} ({vouchers_created} con voucher)") # ============================================================ # WORK ORDERS # ============================================================ wo_data = [ { 'vessel': 'Blue Horizon', 'description': 'Cambio de aceite y filtros motores - servicio 200h', 'estimated_cost': 850, 'actual_cost': 820, 'status': 'done', 'priority': 'normal', 'days_ago': 40 }, { 'vessel': 'Blue Horizon', 'description': 'Revisión sistema de dirección hidráulica - pérdida de fluido detectada', 'estimated_cost': 450, 'actual_cost': None, 'status': 'approved', 'priority': 'urgente', 'days_ago': 5 }, { 'vessel': 'Sol y Mar', 'description': 'Limpieza de teca y aplicación de teak oil', 'estimated_cost': 600, 'actual_cost': 580, 'status': 'done', 'priority': 'normal', 'days_ago': 25 }, { 'vessel': 'Sol y Mar', 'description': 'Actualización software GPS Garmin y radar', 'estimated_cost': 200, 'actual_cost': None, 'status': 'pending', 'priority': 'normal', 'days_ago': 3 }, { 'vessel': 'La Perla', 'description': 'Reemplazo baterías principales (banco 4 x AGM)', 'estimated_cost': 1200, 'actual_cost': 1150, 'status': 'done', 'priority': 'normal', 'days_ago': 30 }, { 'vessel': 'La Perla', 'description': 'Pintura fondo antifouling - temporada anual', 'estimated_cost': 1800, 'actual_cost': None, 'status': 'pending', 'priority': 'normal', 'days_ago': 2 }, { 'vessel': 'Brisa Marina', 'description': 'Cambio impellers bombas de agua dulce y salada', 'estimated_cost': 320, 'actual_cost': 310, 'status': 'done', 'priority': 'normal', 'days_ago': 20 }, { 'vessel': 'Veloce', 'description': 'Servicio anual motores Mercury Verado (aceite, filtros, bujías)', 'estimated_cost': 1400, 'actual_cost': 1380, 'status': 'done', 'priority': 'normal', 'days_ago': 35 }, { 'vessel': 'Veloce', 'description': 'Falla en sistema eléctrico principal — luces de navegación inoperativas. BOTE NO PUEDE SALIR A CHARTER.', 'estimated_cost': 1100, 'actual_cost': None, 'status': 'pending', 'priority': 'emergencia', 'days_ago': 1 }, { 'vessel': 'Lady J', 'description': 'Detailing completo exterior e interior - plan Plus anual', 'estimated_cost': 2200, 'actual_cost': 2200, 'status': 'done', 'priority': 'normal', 'days_ago': 50 }, { 'vessel': 'Lady J', 'description': 'Revisión y ajuste sistema Zeus Pod - vibración anormal a más de 12 nudos', 'estimated_cost': 950, 'actual_cost': None, 'status': 'pending', 'priority': 'urgente', 'days_ago': 1 }, ] wo_created = 0 for wd in wo_data: vessel = vessels[wd['vessel']] existing = WorkOrder.query.filter_by( vessel_id=vessel.id, description=wd['description'] ).first() if existing: continue created_date = now - timedelta(days=wd['days_ago']) wo = WorkOrder( vessel_id=vessel.id, requested_by_company_id=mgmt.id, description=wd['description'], estimated_cost=wd['estimated_cost'], actual_cost=wd['actual_cost'], status=wd['status'], priority=wd.get('priority', 'normal'), created_at=created_date, completed_at=created_date + timedelta(days=3) if wd['status'] == 'done' else None, approved_by_owner_id=1 if wd['status'] in ('approved', 'done') else None ) db.session.add(wo) wo_created += 1 db.session.commit() print(f" [+] Work Orders creadas: {wo_created}") # ============================================================ # ACCOUNTING ENTRIES (desde charters y WOs existentes) # ============================================================ acc_created = 0 # Income: charters completados → 75% va al owner all_charters = Charter.query.filter_by(status='completed').all() for ch in all_charters: exists = AccountingEntry.query.filter_by( vessel_id=ch.vessel_id, reference_type='charter', reference_id=ch.id ).first() if exists: continue if not ch.owner_earnings: continue entry_date = ch.completed_at.date() if ch.completed_at else ( ch.start_datetime.date() if ch.start_datetime else date.today()) db.session.add(AccountingEntry( vessel_id=ch.vessel_id, date=entry_date, entry_type='income', category='charter', description=f'Charter – {ch.charterer_name} – {ch.hours}h', amount=round(ch.owner_earnings, 2), reference_type='charter', reference_id=ch.id, invoice_number=f'CHR-{ch.id:04d}' )) acc_created += 1 # Expense: WOs completadas all_wos = WorkOrder.query.filter_by(status='done').all() for wo in all_wos: exists = AccountingEntry.query.filter_by( vessel_id=wo.vessel_id, reference_type='work_order', reference_id=wo.id ).first() if exists: continue cost = wo.actual_cost or wo.estimated_cost or 0 if cost <= 0: continue entry_date = wo.completed_at.date() if wo.completed_at else date.today() db.session.add(AccountingEntry( vessel_id=wo.vessel_id, date=entry_date, entry_type='expense', category='work_order', description=f'Mantenimiento – {wo.description[:80]}', amount=round(cost, 2), reference_type='work_order', reference_id=wo.id, invoice_number=f'WO-{wo.id:04d}' )) acc_created += 1 # Expense: combustible de vouchers (si tiene fuel_actual_liters) fuel_price = 1.45 # $/litro aprox all_vouchers = Voucher.query.filter(Voucher.fuel_actual_liters > 0).all() for v in all_vouchers: ch = Charter.query.get(v.charter_id) if not ch: continue exists = AccountingEntry.query.filter_by( vessel_id=ch.vessel_id, reference_type='fuel_entry', reference_id=v.id ).first() if exists: continue fuel_cost = round(v.fuel_actual_liters * fuel_price, 2) entry_date = v.issued_at.date() if v.issued_at else date.today() # FuelEntry record fuel_rec = FuelEntry( vessel_id=ch.vessel_id, charter_id=ch.id, date=entry_date, liters=v.fuel_actual_liters, price_per_liter=fuel_price, total_cost=fuel_cost, supplier='Marina Fuel Station', invoice_number=f'FUEL-V{v.id:04d}' ) db.session.add(fuel_rec) db.session.flush() db.session.add(AccountingEntry( vessel_id=ch.vessel_id, date=entry_date, entry_type='expense', category='fuel', description=f'Combustible – {v.fuel_actual_liters:.0f}L charter {ch.charterer_name}', amount=fuel_cost, reference_type='fuel_entry', reference_id=fuel_rec.id, invoice_number=f'FUEL-V{v.id:04d}' )) acc_created += 1 db.session.commit() print(f" [+] Asientos contables generados: {acc_created}") # ============================================================ # RESUMEN # ============================================================ print() print("=" * 55) print(" DATOS CARGADOS EXITOSAMENTE") print("=" * 55) print() print(" ACCESOS:") print(" Admin: admin@fleet.com / admin123") print(" Owner 1: rmitchell@email.com / owner123") print(" Owner 2: msantos@email.com / owner123") print(" Owner 3: cvega@email.com / owner123") print(" Owner 4: jwilliams@email.com / owner123") print() print(" DATOS:") print(f" - {len(owner_companies)} duenos | 6 embarcaciones | 3 capitanes") print(f" - {Charter.query.count()} charters | {WorkOrder.query.count()} work orders | {Voucher.query.count()} vouchers") print(f" - {AccountingEntry.query.count()} asientos contables | {FuelEntry.query.count()} registros de combustible") print() print(" URL: http://localhost:5010") print("=" * 55)