import re import sys part1 = """ 1: from django.db import models 2: from django.contrib.auth.models import User 3: from decimal import Decimal 4: import uuid 5: 6: # Enums 7: class CurrencyEnum(models.TextChoices): 8: CLP = 'CLP', 'Pesos Chilenos' 9: USD = 'USD', 'Dólares Estadounidenses' 10: 11: class TaxTypeEnum(models.TextChoices): 12: EXENTO = 'EXENTO', 'Exento / No Afecto' 13: IVA_19 = 'IVA_19', 'Afecto a IVA (19%)' 14: HONORARIOS = 'HONORARIOS_13_75', 'Boleta de Honorarios (13.75%)' 15: 16: class ExpenseTypeEnum(models.TextChoices): 17: COSTO_DIRECTO = 'COSTO_DIRECTO_PROYECTO', 'Costo Directo Proyecto' 18: GASTO_OPERACIONAL = 'GASTO_OPERACIONAL_OVERHEAD', 'Gasto Operacional Overhead' 19: 20: class ApprovalStatusEnum(models.TextChoices): 21: EN_REVISION = 'EN_REVISION_DOCUMENTAL', 'En Revisión Documental (Paso 1)' 22: SOLICITADO_FINANZAS = 'SOLICITADO_FINANZAS', 'Solicitado a Finanzas (Paso 2)' 23: LIBERADO_PAGADO = 'LIBERADO_PAGADO', 'Pago Liberado (Paso 3)' 24: OBSERVADO_RECHAZADO = 'OBSERVADO_RECHAZADO', 'Observado / Rechazado' 25: 26: class ProjectStatusEnum(models.TextChoices): 27: BORRADOR = 'BORRADOR', 'Borrador / En Diseño' 28: ACTIVO = 'ACTIVO', 'Activo' 29: PAUSADA = 'PAUSADA', 'En Pausa' 30: TERMINADO = 'TERMINADO', 'Terminado' 31: 32: class VehicleOwnershipEnum(models.TextChoices): 33: ARRENDADO = 'ARRENDADO', 'Arrendado (4x4, etc.)' 34: PROPIO = 'PROPIO', 'Propio (Reembolso Kilometraje)' 35: BUS = 'BUS', 'Bus' 36: UBER_TAXI = 'UBER_TAXI', 'Uber/Taxi' 37: 38: class ExpenseReportStatusEnum(models.TextChoices): 39: BORRADOR = 'BORRADOR', 'Borrador' 40: ENVIADO = 'ENVIADO', 'Enviado para Aprobación' 41: APROBADO = 'APROBADO', 'Aprobado' 42: REEMBOLSADO = 'REEMBOLSADO', 'Reembolsado / Pagado' 43: OBSERVADO = 'OBSERVADO', 'Observado / Rechazado' 44: 45: 46: class Project(models.Model): 47: code = models.CharField(max_length=50, unique=True, verbose_name="Código", default='PROY-000') 48: name = models.CharField(max_length=200, verbose_name="Nombre del Proyecto/Centro") 49: description = models.TextField(blank=True, verbose_name="Descripción") 50: 51: manager = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='managed_projects', verbose_name="Encargado / Director") 52: parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children', verbose_name="Portafolio / Proyecto Padre") 53: 54: assigned_budget_clp = models.DecimalField(max_digits=15, decimal_places=2, default=0, verbose_name="Presupuesto Asignado (CLP)") 55: assigned_budget_usd = models.DecimalField(max_digits=15, decimal_places=2, default=0, verbose_name="Presupuesto Asignado (USD)") 56: actual_cost_clp = models.DecimalField(max_digits=15, decimal_places=2, default=0, verbose_name="Costo Real Acumulado (CLP)") 57: 58: payment_method = models.CharField(max_length=150, default="Contrato por Fases / Hitos", verbose_name="Forma de Pago") 59: total_income_clp = models.DecimalField(max_digits=15, decimal_places=2, default=0, verbose_name="Ingresos Totales (CLP)") 60: 61: type = models.CharField(max_length=50, choices=ExpenseTypeEnum.choices, default=ExpenseTypeEnum.COSTO_DIRECTO, verbose_name="Tipo de Centro") 62: status = models.CharField(max_length=50, choices=ProjectStatusEnum.choices, default=ProjectStatusEnum.ACTIVO, verbose_name="Estado de Proyecto") 63: category = models.ForeignKey('Category', on_delete=models.SET_NULL, null=True, blank=True, related_name='projects', verbose_name="Categoría del Proyecto") 64: 65: created_at = models.DateTimeField(auto_now_add=True, verbose_name="Fecha de Creación") 66: updated_at = models.DateTimeField(auto_now=True, verbose_name="Última Modificación") 67: created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='created_projects', verbose_name="Creado por") 68: 69: def __str__(self): 70: return f"{self.code} - {self.name}" 71: 72: @property 73: def total_expenses_clp(self): 74: return self.expenses.aggregate(total=models.Sum('amount_clp'))['total'] or Decimal('0.00') 75: 76: @property 77: def balance_clp(self): 78: return self.total_income_clp - self.total_expenses_clp 79: 80: @property 81: def budget_balance_clp(self): 82: return self.assigned_budget_clp - self.total_expenses_clp 83: 84: class Meta: 85: verbose_name = "Proyecto / Centro de Costo" 86: verbose_name_plural = "Proyectos y Centros" 87: 88: 89: class ProjectPhase(models.Model): 90: project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='phases', verbose_name="Proyecto") 91: phase_number = models.IntegerField(default=1, verbose_name="N° Fase / Hito") 92: name = models.CharField(max_length=150, verbose_name="Nombre de la Fase / Hito") 93: description = models.TextField(blank=True, verbose_name="Descripción de Entregables") 94: planned_amount_clp = models.DecimalField(max_digits=15, decimal_places=2, default=0, verbose_name="Monto Planificado / Pactado (CLP)") 95: income_amount_clp = models.DecimalField(max_digits=15, decimal_places=2, default=0, verbose_name="Monto Ingresado / Cobrado (CLP)") 96: expense_amount_clp = models.DecimalField(max_digits=15, decimal_places=2, default=0, verbose_name="Monto Egresado / Gastado (CLP)") 97: due_date = models.DateField(null=True, blank=True, verbose_name="Fecha Pactada") 98: is_completed = models.BooleanField(default=False, verbose_name="Fase Completada") 99: 100: created_at = models.DateTimeField(auto_now_add=True) 101: updated_at = models.DateTimeField(auto_now=True) 102: 103: class Meta: 104: ordering = ['phase_number'] 105: verbose_name = "Fase / Hito de Pago de Proyecto" 106: verbose_name_plural = "Fases / Hitos de Pago de Proyecto" 107: 108: @property 109: def balance_clp(self): 110: return self.income_amount_clp - self.expense_amount_clp 111: 112: def __str__(self): 113: return f"{self.project.code} - Fase {self.phase_number}: {self.name}" 114: 115: 116: class Category(models.Model): 117: code = models.CharField(max_length=10, unique=True, verbose_name="Código de Categoría") 118: name = models.CharField(max_length=100, verbose_name="Nombre de Categoría") 119: description = models.TextField(blank=True, verbose_name="Descripción") 120: parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children', verbose_name="Categoría Padre") 121: 122: def __str__(self): 123: return f"{self.code}: {self.name}" 124: 125: class Meta: 126: verbose_name = "Categoría" 127: verbose_name_plural = "Categorías" 128: 129: 130: class Vendor(models.Model): 131: rut = models.CharField(max_length=15, unique=True, verbose_name="RUT Proveedor") 132: name = models.CharField(max_length=200, verbose_name="Razón Social / Nombre") 133: business_line = models.CharField(max_length=200, blank=True, null=True, verbose_name="Giro Comercial") 134: 135: bank = models.CharField(max_length=100, blank=True, null=True, verbose_name="Banco") 136: account_type = models.CharField(max_length=50, blank=True, null=True, verbose_name="Tipo de Cuenta") 137: account_number = models.CharField(max_length=50, blank=True, null=True, verbose_name="Número de Cuenta") 138: email = models.EmailField(blank=True, null=True, verbose_name="Correo de Pago/Facturación") 139: 140: payment_terms = models.IntegerField(default=30, verbose_name="Plazo de Pago (Días)") 141: 142: created_at = models.DateTimeField(auto_now_add=True) 143: updated_at = models.DateTimeField(auto_now=True) 144: 145: def __str__(self): 146: return f"{self.rut} - {self.name}" 147: 148: class Meta: 149: verbose_name = "Proveedor" 150: verbose_name_plural = "Proveedores" 151: ordering = ['name'] 152: 153: 154: class ExpenseReport(models.Model): 155: report_code = models.CharField(max_length=50, unique=True, verbose_name="Código Rendición", default="EXP-REP-001") 156: title = models.CharField(max_length=250, verbose_name="Título del Informe / Viaje") 157: user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='expense_reports', verbose_name="Responsable Rendidor") 158: project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='expense_reports', verbose_name="Proyecto Imputado") 159: 160: start_date = models.DateField(verbose_name="Fecha de Inicio") 161: end_date = models.DateField(verbose_name="Fecha de Término") 162: 163: purpose = models.TextField(verbose_name="Propósito / Justificación de la Comisión") 164: status = models.CharField(max_length=30, choices=ExpenseReportStatusEnum.choices, default=ExpenseReportStatusEnum.BORRADOR, verbose_name="Estado Rendición") 165: 166: approved_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='approved_expense_reports', verbose_name="Aprobado por") 167: approved_at = models.DateTimeField(null=True, blank=True, verbose_name="Fecha de Aprobación") 168: 169: notes = models.TextField(blank=True, verbose_name="Observaciones / Comentarios de Cierre") 170: created_at = models.DateTimeField(auto_now_add=True, verbose_name="Fecha Creación") 171: updated_at = models.DateTimeField(auto_now=True, verbose_name="Última Modificación") 172: 173: def __str__(self): 174: return f"{self.report_code} - {self.title} ({self.user.username})" 175: 176: @property 177: def total_amount(self): 178: return sum(exp.total_amount_clp for exp in self.expenses.all()) 179: 180: @property 181: def total_neto(self): 182: return sum(exp.amount_clp for exp in self.expenses.all()) 183: 184: @property 185: def expenses_count(self): 186: return self.expenses.count() 187: 188: class Meta: 189: verbose_name = "Informe de Rendición de Gastos" 190: verbose_name_plural = "Informes de Rendición de Gastos" 191: ordering = ['-created_at'] 192: 193: 194: class Expense(models.Model): 195: expense_code = models.CharField(max_length=50, unique=True, default=uuid.uuid4, verbose_name="Código Transacción") 196: report = models.ForeignKey(ExpenseReport, on_delete=models.SET_NULL, null=True, blank=True, related_name='expenses', verbose_name="Informe de Rendición (Opcional)") 197: project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='expenses', verbose_name="Proyecto") 198: category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True, related_name='expenses', verbose_name="Subcategoría") 199: 200: document_number = models.CharField(max_length=50, blank=True, null=True, verbose_name="N° Folio Boleta / Factura") 201: 202: amount_clp = models.DecimalField(max_digits=15, decimal_places=2, verbose_name="Monto Neto (CLP)") 203: tax_type = models.CharField(max_length=20, choices=TaxTypeEnum.choices, default=TaxTypeEnum.IVA_19, verbose_name="Tipo de Impuesto") 204: tax_amount_clp = models.DecimalField(max_digits=15, decimal_places=2, default=0, verbose_name="Monto Impuesto (CLP)") 205: total_amount_clp = models.DecimalField(max_digits=15, decimal_places=2, default=0, verbose_name="Monto Total (CLP)") 206: 207: amount_usd = models.DecimalField(max_digits=15, decimal_places=2, default=0, verbose_name="Monto Total (USD)") 208: currency = models.CharField(max_length=10, choices=CurrencyEnum.choices, default=CurrencyEnum.CLP, verbose_name="Moneda Original") 209: exchange_rate = models.DecimalField(max_digits=10, decimal_places=2, default=950.0, verbose_name="Tipo de Cambio") 210: 211: description = models.TextField(verbose_name="Detalle de Imputación") 212: date = models.DateField(verbose_name="Fecha del Gasto") 213: 214: vendor = models.ForeignKey(Vendor, on_delete=models.SET_NULL, null=True, blank=True, related_name='expenses', verbose_name="Proveedor / Emisor") 215: 216: region = models.CharField(max_length=100, blank=True, null=True, verbose_name="Región (Chile)") 217: comuna = models.CharField(max_length=100, blank=True, null=True, verbose_name="Comuna") 218: vehicle_ownership = models.CharField(max_length=50, blank=True, null=True, choices=VehicleOwnershipEnum.choices, verbose_name="Modalidad / Vehículo") 219: 220: is_reimbursement = models.BooleanField(default=False, verbose_name="¿Es Rendición / Reembolso de Gastos?") 221: 222: approval_status = models.CharField(max_length=50, choices=ApprovalStatusEnum.choices, default=ApprovalStatusEnum.EN_REVISION, verbose_name="Estado de Aprobación") 223: 224: user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='expenses_created', verbose_name="Registrado por") 225: responsible_person = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='expenses_responsible', verbose_name="Responsable Solicitante / Rendidor") 226: elevated_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='expenses_elevated', verbose_name="Elevado por (Paso 2)") 227: paid_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='expenses_paid', verbose_name="Pagado por (Paso 3)") 228: 229: rejection_reason = models.TextField(blank=True, null=True, verbose_name="Motivo de Observación/Rechazo") 230: paid_at = models.DateTimeField(null=True, blank=True, verbose_name="Fecha de Pago") 231: 232: created_at = models.DateTimeField(auto_now_add=True) 233: updated_at = models.DateTimeField(auto_now=True) 234: 235: def save(self, *args, **kwargs): 236: if not self.amount_usd or self.amount_usd == Decimal('0'): 237: rate = Decimal(str(self.exchange_rate or '950.0')) 238: if self.amount_clp: 239: amount_dec = Decimal(str(self.amount_clp)) 240: self.amount_usd = (amount_dec / rate).quantize(Decimal('0.01')) 241: if not self.total_amount_clp or self.total_amount_clp == Decimal('0'): 242: amount_clp = Decimal(str(self.amount_clp or '0')) 243: tax_clp = Decimal(str(self.tax_amount_clp or '0')) 244: self.total_amount_clp = amount_clp + tax_clp 245: super().save(*args, **kwargs) 246: 247: def __str__(self): 248: return f"{self.expense_code} - {self.amount_clp} CLP ({self.date})" 249: 250: def is_vehicle_category(self): 251: if self.category: 252: return self.category.code == '3101' or 'combustible' in self.category.name.lower() 253: return False 254: 255: class Meta: 256: verbose_name = "Gasto" 257: verbose_name_plural = "Gastos" 258: ordering = ['-date', '-created_at'] 259: 260: 261: class VehicleLog(models.Model): 262: expense = models.OneToOneField(Expense, on_delete=models.CASCADE, related_name='vehicle_log', null=True, blank=True, verbose_name="Gasto Asociado") 263: project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='vehicle_logs', verbose_name="Proyecto") 264: 265: license_plate = models.CharField(max_length=20, verbose_name="Patente Vehículo") 266: driver_name = models.CharField(max_length=150, verbose_name="Nombre Conductor") 267: driver_rut = models.CharField(max_length=20, blank=True, verbose_name="RUT Conductor") 268: 269: odometer_start = models.DecimalField(max_digits=10, decimal_places=1, default=0, verbose_name="Odómetro Inicial (KM)") 270: odometer_end = models.DecimalField(max_digits=10, decimal_places=1, default=0, verbose_name="Odómetro Final (KM)") 271: km_traveled = models.DecimalField(max_digits=10, decimal_places=1, default=0, verbose_name="KM Recorridos") 272: 273: fuel_type = models.CharField(max_length=50, default="Diésel", verbose_name="Tipo Combustible", blank=True) 274: fuel_liters = models.DecimalField(max_digits=8, decimal_places=2, default=0, blank=True, verbose_name="Litros Cargados") 275: 276: route_origin = models.CharField(max_length=150, verbose_name="Origen", default="Oficina / Base") 277: route_destination = models.CharField(max_length=150, verbose_name="Destino / Faena") 278: 279: log_date = models.DateField(verbose_name="Fecha de Registro") 280: notes = models.TextField(blank=True, verbose_name="Observaciones de Ruta") 281: 282: created_at = models.DateTimeField(auto_now_add=True) 283: 284: def save(self, *args, **kwargs): 285: if self.odometer_end and self.odometer_start and self.odometer_end >= self.odometer_start: 286: self.km_traveled = self.odometer_end - self.odometer_start 287: super().save(*args, **kwargs) 288: 289: def __str__(self): 290: return f"{self.license_plate} - {self.driver_name} ({self.km_traveled} KM)" 291: 292: class Meta: 293: verbose_name = "Bitácora de Vehículo" 294: verbose_name_plural = "Bitácoras de Vehículos" 295: ordering = ['-log_date'] 296: 297: 298: class Document(models.Model): 299: expense = models.ForeignKey(Expense, on_delete=models.CASCADE, related_name='documents', verbose_name="Gasto Relacionado") 300: file = models.FileField(upload_to='evidences/', verbose_name="Archivo Adjunto") 301: file_type = models.CharField(max_length=50, blank=True, verbose_name="Tipo de Archivo") 302: title = models.CharField(max_length=200, blank=True, verbose_name="Título del Documento") 303: uploaded_at = models.DateTimeField(auto_now_add=True, verbose_name="Fecha de Subida") 304: uploaded_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, verbose_name="Subido por") 305: 306: def __str__(self): 307: return f"Evidencia para {self.expense.expense_code}" 308: 309: @property 310: def is_image(self): 311: ext = self.file.name.lower().split('.')[-1] if '.' in self.file.name else '' 312: return ext in ['jpg', 'jpeg', 'png', 'gif', 'webp'] 313: 314: @property 315: def is_pdf(self): 316: return self.file.name.lower().endswith('.pdf') 317: 318: class Meta: 319: verbose_name = "Documento Evidencia" 320: verbose_name_plural = "Documentos Evidencias" 321: 322: 323: class UserProfile(models.Model): 324: user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile', verbose_name="Usuario") 325: rut = models.CharField(max_length=20, blank=True, null=True, verbose_name="RUT") 326: job_title = models.CharField(max_length=150, blank=True, null=True, verbose_name="Cargo") 327: description = models.TextField(blank=True, verbose_name="Descripción del Responsable") 328: specialties = models.ManyToManyField('Category', blank=True, related_name='specialists', verbose_name="Especialidades / Habilidades") 329: 330: created_at = models.DateTimeField(auto_now_add=True, verbose_name="Fecha de Registro") 331: updated_at = models.DateTimeField(auto_now=True, verbose_name="Última Modificación") 332: 333: def __str__(self): 334: return f"Perfil de {self.user.username}" 335: 336: class Meta: 337: verbose_name = "Perfil de Usuario" 338: verbose_name_plural = "Perfiles de Usuario" 339: 340: 341: class AuditLog(models.Model): 342: user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Usuario") 343: action = models.CharField(max_length=100, verbose_name="Acción") 344: entity_name = models.CharField(max_length=100, blank=True, null=True, verbose_name="Entidad (Tabla)") 345: entity_id = models.CharField(max_length=100, blank=True, null=True, verbose_name="ID Entidad") 346: field_changed = models.CharField(max_length=100, blank=True, null=True, verbose_name="Campo Modificado") 347: old_value = models.TextField(blank=True, null=True, verbose_name="Valor Anterior") 348: new_value = models.TextField(blank=True, null=True, verbose_name="Valor Nuevo") 349: 350: description = models.TextField(verbose_name="Detalle del Cambio") 351: timestamp = models.DateTimeField(auto_now_add=True, verbose_name="Fecha y Hora") 352: 353: def __str__(self): 354: return f"{self.timestamp} - {self.user.username if self.user else 'Sistema'}: {self.action}" 355: 356: class Meta: 357: ordering = ['-timestamp'] 358: verbose_name = "Log de Auditoría (SAP CDHDR/CDPOS)" 359: verbose_name_plural = "Logs de Auditoría" 360: 361: 362: class SystemSetting(models.Model): 363: global_budget = models.DecimalField(max_digits=15, decimal_places=2, default=25000000.00, verbose_name="Presupuesto General I+D+i") 364: usd_exchange_rate = models.DecimalField(max_digits=10, decimal_places=2, default=950.0, verbose_name="Tasa USD/CLP por Defecto") 365: iva_rate = models.DecimalField(max_digits=5, decimal_places=2, default=19.00, verbose_name="Tasa de IVA (% Dinámico SII)") 366: 367: def __str__(self): 368: return "Configuración Global EFINTEL" 369: 370: class Meta: 371: verbose_name = "Configuración del Sistema" 372: verbose_name_plural = "Configuraciones del Sistema" 373: """ out_lines = [] for line in part1.split("\n"): if re.match(r'^\d+:\s', line): out_lines.append(line.split(": ", 1)[1]) with open('expenses/models.py', 'w', encoding='utf-8') as f: f.write("\n".join(out_lines))