Files
alro65 5b7b41aa50 Initial commit: Fleet Management app with security hardening and background launcher
- Flask app with SQLAlchemy, Flask-Login, Flask-Mail
- Admin/owner roles, vessel management, charters, work orders
- Background launcher (Iniciar.vbs) runs server without terminal window
- Root redirect fixed: / → /login
- debug=False, use_reloader=False for pythonw.exe compatibility

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 02:54:10 -04:00

192 lines
9.2 KiB
Python

from flask_login import UserMixin
from app import db
from datetime import datetime
class Company(db.Model):
__tablename__ = 'companies'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(200), nullable=False)
type = db.Column(db.String(20), nullable=False) # 'management', 'owner'
email = db.Column(db.String(100))
phone = db.Column(db.String(20))
created_at = db.Column(db.DateTime, default=datetime.utcnow)
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(100), unique=True, nullable=False)
password_hash = db.Column(db.String(200), nullable=False)
company_id = db.Column(db.Integer, db.ForeignKey('companies.id'))
role = db.Column(db.String(20), default='admin') # 'admin', 'captain'
is_active = db.Column(db.Boolean, default=True)
is_super_admin = db.Column(db.Boolean, default=False)
company = db.relationship('Company', backref='users')
class Vessel(db.Model):
__tablename__ = 'vessels'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
hin = db.Column(db.String(50))
make = db.Column(db.String(50))
model = db.Column(db.String(50))
length = db.Column(db.Float)
engines = db.Column(db.String(200))
fuel_consumption_14knots = db.Column(db.Float) # L/h
owner_company_id = db.Column(db.Integer, db.ForeignKey('companies.id'))
management_company_id = db.Column(db.Integer, db.ForeignKey('companies.id'))
management_company = db.relationship('Company', foreign_keys=[management_company_id], backref='managed_vessels')
plan_id = db.Column(db.Integer)
charter_percentage = db.Column(db.Float, default=25.0) # % para management
base_rate_4h = db.Column(db.Float)
hourly_rate_extra = db.Column(db.Float)
max_passengers = db.Column(db.Integer, default=12)
is_active = db.Column(db.Boolean, default=True)
class Captain(db.Model):
__tablename__ = 'captains'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
phone = db.Column(db.String(20))
license_number = db.Column(db.String(50))
hourly_rate = db.Column(db.Float)
company_id = db.Column(db.Integer, db.ForeignKey('companies.id'))
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
license_type = db.Column(db.String(20), default='private') # 'private' | 'six_pack' | 'master'
class Route(db.Model):
__tablename__ = 'routes'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
distance_nm = db.Column(db.Float)
time_hours_14knots = db.Column(db.Float)
points_of_interest = db.Column(db.Text)
deviation_charge_usd = db.Column(db.Float, default=50)
class Charter(db.Model):
__tablename__ = 'charters'
id = db.Column(db.Integer, primary_key=True)
vessel_id = db.Column(db.Integer, db.ForeignKey('vessels.id'))
route_id = db.Column(db.Integer, db.ForeignKey('routes.id'))
charterer_name = db.Column(db.String(100))
charterer_phone = db.Column(db.String(20))
charterer_email = db.Column(db.String(100))
start_datetime = db.Column(db.DateTime)
hours = db.Column(db.Float)
total_base_rate = db.Column(db.Float)
management_percentage = db.Column(db.Float)
management_earnings = db.Column(db.Float)
owner_earnings = db.Column(db.Float)
status = db.Column(db.String(20), default='draft') # draft, signed, completed, paid
contract_pdf = db.Column(db.String(200))
completed_at = db.Column(db.DateTime)
insurance_rider_number = db.Column(db.String(60))
insurer_name = db.Column(db.String(100))
coverage_amount = db.Column(db.Float)
damage_waiver = db.Column(db.Float, default=0)
captain_id = db.Column(db.Integer, db.ForeignKey('captains.id'))
captain = db.relationship('Captain', backref='charters')
vessel = db.relationship('Vessel', backref='charters')
route = db.relationship('Route', backref='charters')
class WorkOrder(db.Model):
__tablename__ = 'work_orders'
id = db.Column(db.Integer, primary_key=True)
vessel_id = db.Column(db.Integer, db.ForeignKey('vessels.id'))
requested_by_company_id = db.Column(db.Integer, db.ForeignKey('companies.id'))
approved_by_owner_id = db.Column(db.Integer)
description = db.Column(db.Text)
estimated_cost = db.Column(db.Float)
actual_cost = db.Column(db.Float)
status = db.Column(db.String(20), default='pending') # pending, approved, done, rejected
priority = db.Column(db.String(20), default='normal') # normal, urgente, emergencia
invoice_number = db.Column(db.String(60)) # número de factura/invoice
notified_at = db.Column(db.DateTime)
approved_at = db.Column(db.DateTime) # timestamp de aprobacion del owner
approved_by_name = db.Column(db.String(100)) # nombre del owner que aprobó
rejected_at = db.Column(db.DateTime)
rejection_reason = db.Column(db.String(300))
created_at = db.Column(db.DateTime, default=datetime.utcnow)
completed_at = db.Column(db.DateTime)
vessel = db.relationship('Vessel', backref='work_orders')
class Voucher(db.Model):
__tablename__ = 'vouchers'
id = db.Column(db.Integer, primary_key=True)
charter_id = db.Column(db.Integer, db.ForeignKey('charters.id'))
total_charged = db.Column(db.Float)
fuel_actual_liters = db.Column(db.Float)
deviation_charged = db.Column(db.Float, default=0)
speed_extra_charged = db.Column(db.Float, default=0)
tip_amount = db.Column(db.Float)
tip_percentage = db.Column(db.Float, default=18)
issued_at = db.Column(db.DateTime, default=datetime.utcnow)
paid_at = db.Column(db.DateTime)
charter = db.relationship('Charter', backref='voucher')
class AccountingVessel(db.Model):
__tablename__ = 'accounting_vessel'
id = db.Column(db.Integer, primary_key=True)
vessel_id = db.Column(db.Integer, db.ForeignKey('vessels.id'))
month = db.Column(db.Integer)
year = db.Column(db.Integer)
charter_revenue = db.Column(db.Float, default=0)
plan_revenue = db.Column(db.Float, default=0)
fuel_cost = db.Column(db.Float, default=0)
maintenance_cost = db.Column(db.Float, default=0)
cleaning_cost = db.Column(db.Float, default=0)
detailing_cost = db.Column(db.Float, default=0)
teak_cost = db.Column(db.Float, default=0)
net_profit = db.Column(db.Float, default=0)
# ── Ledger de transacciones por embarcación ──────────────────────────
class AccountingEntry(db.Model):
__tablename__ = 'accounting_entries'
id = db.Column(db.Integer, primary_key=True)
vessel_id = db.Column(db.Integer, db.ForeignKey('vessels.id'), nullable=False)
date = db.Column(db.Date, nullable=False)
entry_type = db.Column(db.String(10), nullable=False) # 'income' | 'expense'
category = db.Column(db.String(30), nullable=False)
# income: 'charter' | 'plan_subscription' | 'other_income'
# expense: 'work_order' | 'fuel' | 'cleaning' | 'detailing' | 'teak' | 'marina' | 'other_expense'
description = db.Column(db.String(300))
amount = db.Column(db.Float, nullable=False)
invoice_number = db.Column(db.String(60))
reference_type = db.Column(db.String(20)) # 'charter' | 'work_order' | 'fuel_entry' | None
reference_id = db.Column(db.Integer)
notes = db.Column(db.Text)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
vessel = db.relationship('Vessel', backref='accounting_entries')
# ── Registro de combustible ──────────────────────────────────────────
class FuelEntry(db.Model):
__tablename__ = 'fuel_entries'
id = db.Column(db.Integer, primary_key=True)
vessel_id = db.Column(db.Integer, db.ForeignKey('vessels.id'), nullable=False)
charter_id = db.Column(db.Integer, db.ForeignKey('charters.id'), nullable=True)
date = db.Column(db.Date, nullable=False)
liters = db.Column(db.Float)
price_per_liter = db.Column(db.Float)
total_cost = db.Column(db.Float, nullable=False)
supplier = db.Column(db.String(100))
invoice_number = db.Column(db.String(60))
notes = db.Column(db.Text)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
vessel = db.relationship('Vessel', backref='fuel_entries')
charter = db.relationship('Charter', backref='fuel_entries')
# ── Documentos adjuntos (invoices, contratos, fotos) ─────────────────
class Document(db.Model):
__tablename__ = 'documents'
id = db.Column(db.Integer, primary_key=True)
vessel_id = db.Column(db.Integer, db.ForeignKey('vessels.id'))
reference_type = db.Column(db.String(20)) # 'charter'|'work_order'|'fuel_entry'|'general'
reference_id = db.Column(db.Integer)
doc_type = db.Column(db.String(20)) # 'invoice'|'receipt'|'contract'|'photo'|'other'
filename = db.Column(db.String(200))
original_name = db.Column(db.String(200))
file_size = db.Column(db.Integer)
uploaded_at = db.Column(db.DateTime, default=datetime.utcnow)
notes = db.Column(db.Text)
vessel = db.relationship('Vessel', backref='documents')