Initial commit: Horux Strategy Platform
- Laravel 11 backend with API REST - React 18 + TypeScript + Vite frontend - Multi-parser architecture for accounting systems (CONTPAQi, Aspel, SAP) - 27+ financial metrics calculation - PDF report generation with Browsershot - Complete documentation (10 documents) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
57
backend/app/Models/Balanza.php
Normal file
57
backend/app/Models/Balanza.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
class Balanza extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'cliente_id',
|
||||
'periodo_inicio',
|
||||
'periodo_fin',
|
||||
'sistema_origen',
|
||||
'archivo_original',
|
||||
'status',
|
||||
'error_mensaje',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'periodo_inicio' => 'date',
|
||||
'periodo_fin' => 'date',
|
||||
];
|
||||
}
|
||||
|
||||
public function cliente(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Cliente::class);
|
||||
}
|
||||
|
||||
public function cuentas(): HasMany
|
||||
{
|
||||
return $this->hasMany(Cuenta::class);
|
||||
}
|
||||
|
||||
public function reportes(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Reporte::class, 'reporte_balanza');
|
||||
}
|
||||
|
||||
public function cuentasActivas(): HasMany
|
||||
{
|
||||
return $this->hasMany(Cuenta::class)->where('excluida', false);
|
||||
}
|
||||
|
||||
public function cuentasPadre(): HasMany
|
||||
{
|
||||
return $this->hasMany(Cuenta::class)->where('es_cuenta_padre', true);
|
||||
}
|
||||
}
|
||||
36
backend/app/Models/CategoriaContable.php
Normal file
36
backend/app/Models/CategoriaContable.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class CategoriaContable extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'categorias_contables';
|
||||
|
||||
protected $fillable = [
|
||||
'reporte_contable_id',
|
||||
'nombre',
|
||||
'orden',
|
||||
];
|
||||
|
||||
public function reporteContable(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ReporteContable::class);
|
||||
}
|
||||
|
||||
public function cuentas(): HasMany
|
||||
{
|
||||
return $this->hasMany(Cuenta::class);
|
||||
}
|
||||
|
||||
public function reglasMapeoo(): HasMany
|
||||
{
|
||||
return $this->hasMany(ReglaMapeoo::class);
|
||||
}
|
||||
}
|
||||
58
backend/app/Models/Cliente.php
Normal file
58
backend/app/Models/Cliente.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Cliente extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'nombre_empresa',
|
||||
'logo',
|
||||
'giro_id',
|
||||
'moneda',
|
||||
'configuracion',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'configuracion' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
public function giro(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Giro::class);
|
||||
}
|
||||
|
||||
public function usuarios(): HasMany
|
||||
{
|
||||
return $this->hasMany(User::class);
|
||||
}
|
||||
|
||||
public function balanzas(): HasMany
|
||||
{
|
||||
return $this->hasMany(Balanza::class);
|
||||
}
|
||||
|
||||
public function reportes(): HasMany
|
||||
{
|
||||
return $this->hasMany(Reporte::class);
|
||||
}
|
||||
|
||||
public function mapeoCuentas(): HasMany
|
||||
{
|
||||
return $this->hasMany(MapeoCuenta::class);
|
||||
}
|
||||
|
||||
public function permisosEmpleado(): HasMany
|
||||
{
|
||||
return $this->hasMany(PermisoEmpleado::class);
|
||||
}
|
||||
}
|
||||
109
backend/app/Models/Cuenta.php
Normal file
109
backend/app/Models/Cuenta.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Cuenta extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'balanza_id',
|
||||
'codigo',
|
||||
'nombre',
|
||||
'nivel',
|
||||
'reporte_contable_id',
|
||||
'categoria_contable_id',
|
||||
'cuenta_padre_id',
|
||||
'saldo_inicial_deudor',
|
||||
'saldo_inicial_acreedor',
|
||||
'cargos',
|
||||
'abonos',
|
||||
'saldo_final_deudor',
|
||||
'saldo_final_acreedor',
|
||||
'excluida',
|
||||
'es_cuenta_padre',
|
||||
'requiere_revision',
|
||||
'nota_revision',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'saldo_inicial_deudor' => 'decimal:2',
|
||||
'saldo_inicial_acreedor' => 'decimal:2',
|
||||
'cargos' => 'decimal:2',
|
||||
'abonos' => 'decimal:2',
|
||||
'saldo_final_deudor' => 'decimal:2',
|
||||
'saldo_final_acreedor' => 'decimal:2',
|
||||
'excluida' => 'boolean',
|
||||
'es_cuenta_padre' => 'boolean',
|
||||
'requiere_revision' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function balanza(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Balanza::class);
|
||||
}
|
||||
|
||||
public function reporteContable(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ReporteContable::class);
|
||||
}
|
||||
|
||||
public function categoriaContable(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(CategoriaContable::class);
|
||||
}
|
||||
|
||||
public function cuentaPadre(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Cuenta::class, 'cuenta_padre_id');
|
||||
}
|
||||
|
||||
public function cuentasHijo(): HasMany
|
||||
{
|
||||
return $this->hasMany(Cuenta::class, 'cuenta_padre_id');
|
||||
}
|
||||
|
||||
public function getSaldoInicialNetoAttribute(): float
|
||||
{
|
||||
return $this->saldo_inicial_deudor - $this->saldo_inicial_acreedor;
|
||||
}
|
||||
|
||||
public function getSaldoFinalNetoAttribute(): float
|
||||
{
|
||||
return $this->saldo_final_deudor - $this->saldo_final_acreedor;
|
||||
}
|
||||
|
||||
public function getMovimientoNetoAttribute(): float
|
||||
{
|
||||
return $this->cargos - $this->abonos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalcula el saldo considerando solo cuentas hijo activas
|
||||
*/
|
||||
public function recalcularSaldoDesdeHijos(): void
|
||||
{
|
||||
if (!$this->es_cuenta_padre) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hijosActivos = $this->cuentasHijo()->where('excluida', false)->get();
|
||||
|
||||
$this->saldo_inicial_deudor = $hijosActivos->sum('saldo_inicial_deudor');
|
||||
$this->saldo_inicial_acreedor = $hijosActivos->sum('saldo_inicial_acreedor');
|
||||
$this->cargos = $hijosActivos->sum('cargos');
|
||||
$this->abonos = $hijosActivos->sum('abonos');
|
||||
$this->saldo_final_deudor = $hijosActivos->sum('saldo_final_deudor');
|
||||
$this->saldo_final_acreedor = $hijosActivos->sum('saldo_final_acreedor');
|
||||
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
34
backend/app/Models/Giro.php
Normal file
34
backend/app/Models/Giro.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Giro extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'nombre',
|
||||
'activo',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'activo' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function clientes(): HasMany
|
||||
{
|
||||
return $this->hasMany(Cliente::class);
|
||||
}
|
||||
|
||||
public function umbrales(): HasMany
|
||||
{
|
||||
return $this->hasMany(Umbral::class);
|
||||
}
|
||||
}
|
||||
31
backend/app/Models/MapeoCuenta.php
Normal file
31
backend/app/Models/MapeoCuenta.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class MapeoCuenta extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'mapeo_cuentas';
|
||||
|
||||
protected $fillable = [
|
||||
'cliente_id',
|
||||
'codigo_patron',
|
||||
'categoria_contable_id',
|
||||
'notas',
|
||||
];
|
||||
|
||||
public function cliente(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Cliente::class);
|
||||
}
|
||||
|
||||
public function categoriaContable(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(CategoriaContable::class);
|
||||
}
|
||||
}
|
||||
42
backend/app/Models/PermisoEmpleado.php
Normal file
42
backend/app/Models/PermisoEmpleado.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class PermisoEmpleado extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'permisos_empleado';
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'cliente_id',
|
||||
'permisos',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'permisos' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function cliente(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Cliente::class);
|
||||
}
|
||||
|
||||
public function tienePermiso(string $permiso): bool
|
||||
{
|
||||
return in_array($permiso, $this->permisos ?? []);
|
||||
}
|
||||
}
|
||||
73
backend/app/Models/ReglaMapeo.php
Normal file
73
backend/app/Models/ReglaMapeo.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class ReglaMapeo extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'reglas_mapeo';
|
||||
|
||||
protected $fillable = [
|
||||
'sistema_origen',
|
||||
'cuenta_padre_codigo',
|
||||
'rango_inicio',
|
||||
'rango_fin',
|
||||
'patron_regex',
|
||||
'reporte_contable_id',
|
||||
'categoria_contable_id',
|
||||
'prioridad',
|
||||
'activo',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'activo' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function reporteContable(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ReporteContable::class);
|
||||
}
|
||||
|
||||
public function categoriaContable(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(CategoriaContable::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica si un código de cuenta coincide con esta regla
|
||||
*/
|
||||
public function coincideCon(string $codigo): bool
|
||||
{
|
||||
// Si tiene patrón regex, usarlo
|
||||
if ($this->patron_regex) {
|
||||
return (bool) preg_match($this->patron_regex, $codigo);
|
||||
}
|
||||
|
||||
// Si tiene rango, verificar
|
||||
if ($this->rango_inicio && $this->rango_fin) {
|
||||
$codigoNumerico = $this->codigoANumero($codigo);
|
||||
$inicioNumerico = $this->codigoANumero($this->rango_inicio);
|
||||
$finNumerico = $this->codigoANumero($this->rango_fin);
|
||||
|
||||
return $codigoNumerico >= $inicioNumerico && $codigoNumerico <= $finNumerico;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convierte código tipo "001-100-000" a número para comparación
|
||||
*/
|
||||
private function codigoANumero(string $codigo): int
|
||||
{
|
||||
return (int) str_replace(['-', '.', ' '], '', $codigo);
|
||||
}
|
||||
}
|
||||
55
backend/app/Models/Reporte.php
Normal file
55
backend/app/Models/Reporte.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
class Reporte extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'cliente_id',
|
||||
'nombre',
|
||||
'periodo_tipo',
|
||||
'periodo_inicio',
|
||||
'periodo_fin',
|
||||
'fecha_generacion',
|
||||
'data_calculada',
|
||||
'pdf_path',
|
||||
'status',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'periodo_inicio' => 'date',
|
||||
'periodo_fin' => 'date',
|
||||
'fecha_generacion' => 'datetime',
|
||||
'data_calculada' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
public function cliente(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Cliente::class);
|
||||
}
|
||||
|
||||
public function balanzas(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Balanza::class, 'reporte_balanza');
|
||||
}
|
||||
|
||||
public function getMetrica(string $nombre): ?array
|
||||
{
|
||||
return $this->data_calculada['metricas'][$nombre] ?? null;
|
||||
}
|
||||
|
||||
public function getEstadoFinanciero(string $tipo): ?array
|
||||
{
|
||||
return $this->data_calculada['estados_financieros'][$tipo] ?? null;
|
||||
}
|
||||
}
|
||||
28
backend/app/Models/ReporteContable.php
Normal file
28
backend/app/Models/ReporteContable.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class ReporteContable extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'reportes_contables';
|
||||
|
||||
protected $fillable = [
|
||||
'nombre',
|
||||
];
|
||||
|
||||
public function categorias(): HasMany
|
||||
{
|
||||
return $this->hasMany(CategoriaContable::class)->orderBy('orden');
|
||||
}
|
||||
|
||||
public function cuentas(): HasMany
|
||||
{
|
||||
return $this->hasMany(Cuenta::class);
|
||||
}
|
||||
}
|
||||
57
backend/app/Models/Umbral.php
Normal file
57
backend/app/Models/Umbral.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Umbral extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'umbrales';
|
||||
|
||||
protected $fillable = [
|
||||
'metrica',
|
||||
'muy_positivo',
|
||||
'positivo',
|
||||
'neutral',
|
||||
'negativo',
|
||||
'muy_negativo',
|
||||
'giro_id',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'muy_positivo' => 'decimal:4',
|
||||
'positivo' => 'decimal:4',
|
||||
'neutral' => 'decimal:4',
|
||||
'negativo' => 'decimal:4',
|
||||
'muy_negativo' => 'decimal:4',
|
||||
];
|
||||
}
|
||||
|
||||
public function giro(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Giro::class);
|
||||
}
|
||||
|
||||
public function evaluarValor(float $valor): string
|
||||
{
|
||||
if ($this->muy_positivo !== null && $valor >= $this->muy_positivo) {
|
||||
return 'muy_positivo';
|
||||
}
|
||||
if ($this->positivo !== null && $valor >= $this->positivo) {
|
||||
return 'positivo';
|
||||
}
|
||||
if ($this->neutral !== null && $valor >= $this->neutral) {
|
||||
return 'neutral';
|
||||
}
|
||||
if ($this->negativo !== null && $valor >= $this->negativo) {
|
||||
return 'negativo';
|
||||
}
|
||||
return 'muy_negativo';
|
||||
}
|
||||
}
|
||||
75
backend/app/Models/User.php
Normal file
75
backend/app/Models/User.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use HasApiTokens, HasFactory, Notifiable;
|
||||
|
||||
protected $fillable = [
|
||||
'nombre',
|
||||
'email',
|
||||
'password',
|
||||
'role',
|
||||
'cliente_id',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
|
||||
public function cliente(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Cliente::class);
|
||||
}
|
||||
|
||||
public function permisosEmpleado(): HasMany
|
||||
{
|
||||
return $this->hasMany(PermisoEmpleado::class);
|
||||
}
|
||||
|
||||
public function isAdmin(): bool
|
||||
{
|
||||
return $this->role === 'admin';
|
||||
}
|
||||
|
||||
public function isAnalista(): bool
|
||||
{
|
||||
return $this->role === 'analista';
|
||||
}
|
||||
|
||||
public function isCliente(): bool
|
||||
{
|
||||
return $this->role === 'cliente';
|
||||
}
|
||||
|
||||
public function isEmpleado(): bool
|
||||
{
|
||||
return $this->role === 'empleado';
|
||||
}
|
||||
|
||||
public function canAccessCliente(int $clienteId): bool
|
||||
{
|
||||
if ($this->isAdmin() || $this->isAnalista()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->cliente_id === $clienteId;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user