package models import ( "time" "github.com/sujit-baniya/fiber-boilerplate/app" "gorm.io/gorm" ) // ─── Cuenta (plan de cuentas / categoría contable) ─────────────────────────── type Cuenta struct { gorm.Model Codigo string `json:"codigo" gorm:"column:codigo;size:20"` Nombre string `json:"nombre" gorm:"column:nombre;size:255;not null"` Tipo string `json:"tipo" gorm:"column:tipo;size:20;not null"` // ingreso | egreso Color string `json:"color" gorm:"column:color;size:7"` Activo bool `json:"activo" gorm:"column:activo;default:true"` } func (Cuenta) TableName() string { return "contab_cuentas" } // ─── Entidad (cliente, proveedor, empresa, persona) ────────────────────────── type Entidad struct { gorm.Model Nombre string `json:"nombre" gorm:"column:nombre;size:255;not null"` Tipo string `json:"tipo" gorm:"column:tipo;size:20"` // cliente | proveedor | ambos Documento string `json:"documento" gorm:"column:documento;size:50"` Email string `json:"email" gorm:"column:email;size:255"` Telefono string `json:"telefono" gorm:"column:telefono;size:50"` Contacto string `json:"contacto" gorm:"column:contacto;size:255"` Notas string `json:"notas" gorm:"column:notas;type:text"` Activo bool `json:"activo" gorm:"column:activo;default:true"` } func (Entidad) TableName() string { return "contab_entidades" } // ─── Transaccion (ingreso / egreso) ────────────────────────────────────────── type Transaccion struct { gorm.Model Fecha time.Time `json:"fecha" gorm:"column:fecha;not null"` Tipo string `json:"tipo" gorm:"column:tipo;size:10;not null"` // ingreso | egreso Descripcion string `json:"descripcion" gorm:"column:descripcion;type:text"` Valor float64 `json:"valor" gorm:"column:valor;not null"` CuentaID *uint `json:"cuenta_id" gorm:"column:cuenta_id;index"` Cuenta *Cuenta `json:"cuenta" gorm:"foreignKey:CuentaID"` EntidadID *uint `json:"entidad_id" gorm:"column:entidad_id;index"` Entidad *Entidad `json:"entidad" gorm:"foreignKey:EntidadID"` FormaPago string `json:"forma_pago" gorm:"column:forma_pago;size:30"` // transferencia | efectivo | tarjeta | cheque | otro Estado string `json:"estado" gorm:"column:estado;size:20;default:'registrada'"` Notas string `json:"notas" gorm:"column:notas;type:text"` } func (Transaccion) TableName() string { return "contab_transacciones" } // ─── CuentaCobro (cuentas por cobrar) ──────────────────────────────────────── type CuentaCobro struct { gorm.Model EntidadID uint `json:"entidad_id" gorm:"column:entidad_id;index;not null"` Entidad Entidad `json:"entidad" gorm:"foreignKey:EntidadID"` Fecha time.Time `json:"fecha" gorm:"column:fecha;not null"` Descripcion string `json:"descripcion" gorm:"column:descripcion;type:text"` Valor float64 `json:"valor" gorm:"column:valor;not null"` Estado string `json:"estado" gorm:"column:estado;size:20;default:'pendiente'"` // pendiente | pagado | parcial FechaVencimiento *time.Time `json:"fecha_vencimiento" gorm:"column:fecha_vencimiento"` FechaPago *time.Time `json:"fecha_pago" gorm:"column:fecha_pago"` TransaccionID *uint `json:"transaccion_id" gorm:"column:transaccion_id"` Transaccion *Transaccion `json:"transaccion" gorm:"foreignKey:TransaccionID"` Notas string `json:"notas" gorm:"column:notas;type:text"` } func (CuentaCobro) TableName() string { return "contab_cuentas_cobro" } // ─── CuentaPagar (cuentas por pagar) ───────────────────────────────────────── type CuentaPagar struct { gorm.Model EntidadID uint `json:"entidad_id" gorm:"column:entidad_id;index;not null"` Entidad Entidad `json:"entidad" gorm:"foreignKey:EntidadID"` Fecha time.Time `json:"fecha" gorm:"column:fecha;not null"` Descripcion string `json:"descripcion" gorm:"column:descripcion;type:text"` Valor float64 `json:"valor" gorm:"column:valor;not null"` Vencimiento *time.Time `json:"vencimiento" gorm:"column:vencimiento"` Estado string `json:"estado" gorm:"column:estado;size:20;default:'pendiente'"` // pendiente | pagado | parcial FechaPago *time.Time `json:"fecha_pago" gorm:"column:fecha_pago"` TransaccionID *uint `json:"transaccion_id" gorm:"column:transaccion_id"` Transaccion *Transaccion `json:"transaccion" gorm:"foreignKey:TransaccionID"` Notas string `json:"notas" gorm:"column:notas;type:text"` } func (CuentaPagar) TableName() string { return "contab_cuentas_pagar" } // ─── ConsolidadoMensual ────────────────────────────────────────────────────── type ConsolidadoMensual struct { gorm.Model Anio int `json:"anio" gorm:"column:anio;not null"` Mes int `json:"mes" gorm:"column:mes;not null"` TotalIngresos float64 `json:"total_ingresos" gorm:"column:total_ingresos;default:0"` TotalEgresos float64 `json:"total_egresos" gorm:"column:total_egresos;default:0"` TotalRetenciones float64 `json:"total_retenciones" gorm:"column:total_retenciones;default:0"` Resultado float64 `json:"resultado" gorm:"column:resultado;default:0"` } func (ConsolidadoMensual) TableName() string { return "contab_consolidado" } // ============================================================================= // ─── CRUD: Cuenta ─────────────────────────────────────────────────────────── // ============================================================================= func GetAllCuentas(limit, offset int, search string) ([]Cuenta, int64, error) { var items []Cuenta var total int64 db := app.Http.Database.DB.Model(&Cuenta{}) if search != "" { db = db.Where("nombre ILIKE ? OR codigo ILIKE ?", "%"+search+"%", "%"+search+"%") } if err := db.Count(&total).Error; err != nil { return nil, 0, err } if err := db.Order("tipo ASC, nombre ASC").Limit(limit).Offset(offset).Find(&items).Error; err != nil { return nil, 0, err } return items, total, nil } func GetAllCuentasSelect() ([]Cuenta, error) { var items []Cuenta if err := app.Http.Database.DB.Where("activo = ?", true).Order("nombre ASC").Find(&items).Error; err != nil { return nil, err } return items, nil } func CreateCuenta(c *Cuenta) error { return app.Http.Database.DB.Create(c).Error } func UpdateCuenta(c *Cuenta) error { return app.Http.Database.DB.Model(&Cuenta{}).Where("id = ?", c.ID).Updates(map[string]interface{}{ "codigo": c.Codigo, "nombre": c.Nombre, "tipo": c.Tipo, "color": c.Color, "activo": c.Activo, }).Error } func DeleteCuenta(id uint) error { return app.Http.Database.DB.Delete(&Cuenta{}, id).Error } // ============================================================================= // ─── CRUD: Entidad ────────────────────────────────────────────────────────── // ============================================================================= func GetAllEntidades(limit, offset int, search string) ([]Entidad, int64, error) { var items []Entidad var total int64 db := app.Http.Database.DB.Model(&Entidad{}) if search != "" { db = db.Where("nombre ILIKE ? OR documento ILIKE ? OR email ILIKE ?", "%"+search+"%", "%"+search+"%", "%"+search+"%") } if err := db.Count(&total).Error; err != nil { return nil, 0, err } if err := db.Order("nombre ASC").Limit(limit).Offset(offset).Find(&items).Error; err != nil { return nil, 0, err } return items, total, nil } func GetAllEntidadesSelect() ([]Entidad, error) { var items []Entidad if err := app.Http.Database.DB.Where("activo = ?", true).Order("nombre ASC").Find(&items).Error; err != nil { return nil, err } return items, nil } func CreateEntidad(e *Entidad) error { return app.Http.Database.DB.Create(e).Error } func UpdateEntidad(e *Entidad) error { return app.Http.Database.DB.Model(&Entidad{}).Where("id = ?", e.ID).Updates(map[string]interface{}{ "nombre": e.Nombre, "tipo": e.Tipo, "documento": e.Documento, "email": e.Email, "telefono": e.Telefono, "contacto": e.Contacto, "notas": e.Notas, "activo": e.Activo, }).Error } func DeleteEntidad(id uint) error { return app.Http.Database.DB.Delete(&Entidad{}, id).Error } // ============================================================================= // ─── CRUD: Transaccion ────────────────────────────────────────────────────── // ============================================================================= func GetAllTransacciones(limit, offset int, search string, filtroTipo string, mes int, anio int) ([]Transaccion, int64, error) { var items []Transaccion var total int64 db := app.Http.Database.DB.Model(&Transaccion{}).Preload("Cuenta").Preload("Entidad") if search != "" { db = db.Where("descripcion ILIKE ?", "%"+search+"%") } if filtroTipo != "" { db = db.Where("tipo = ?", filtroTipo) } if mes > 0 && anio > 0 { db = db.Where("EXTRACT(MONTH FROM fecha) = ? AND EXTRACT(YEAR FROM fecha) = ?", mes, anio) } if err := db.Count(&total).Error; err != nil { return nil, 0, err } if err := db.Order("fecha DESC, created_at DESC").Limit(limit).Offset(offset).Find(&items).Error; err != nil { return nil, 0, err } return items, total, nil } func CreateTransaccion(t *Transaccion) error { return app.Http.Database.DB.Create(t).Error } func UpdateTransaccion(t *Transaccion) error { return app.Http.Database.DB.Model(&Transaccion{}).Where("id = ?", t.ID).Updates(map[string]interface{}{ "fecha": t.Fecha, "tipo": t.Tipo, "descripcion": t.Descripcion, "valor": t.Valor, "cuenta_id": t.CuentaID, "entidad_id": t.EntidadID, "forma_pago": t.FormaPago, "estado": t.Estado, "notas": t.Notas, }).Error } func DeleteTransaccion(id uint) error { return app.Http.Database.DB.Delete(&Transaccion{}, id).Error } // ============================================================================= // ─── CRUD: CuentaCobro ────────────────────────────────────────────────────── // ============================================================================= func GetAllCuentasCobro(limit, offset int, search string, estado string) ([]CuentaCobro, int64, error) { var items []CuentaCobro var total int64 db := app.Http.Database.DB.Model(&CuentaCobro{}).Preload("Entidad").Preload("Transaccion") if search != "" { db = db.Joins("JOIN contab_entidades ON contab_entidades.id = contab_cuentas_cobro.entidad_id"). Where("contab_entidades.nombre ILIKE ? OR contab_cuentas_cobro.descripcion ILIKE ?", "%"+search+"%", "%"+search+"%") } if estado != "" { db = db.Where("contab_cuentas_cobro.estado = ?", estado) } if err := db.Count(&total).Error; err != nil { return nil, 0, err } if err := db.Order("contab_cuentas_cobro.fecha DESC").Limit(limit).Offset(offset).Find(&items).Error; err != nil { return nil, 0, err } return items, total, nil } func CreateCuentaCobro(cc *CuentaCobro) error { return app.Http.Database.DB.Create(cc).Error } func UpdateCuentaCobro(cc *CuentaCobro) error { return app.Http.Database.DB.Model(&CuentaCobro{}).Where("id = ?", cc.ID).Updates(map[string]interface{}{ "entidad_id": cc.EntidadID, "fecha": cc.Fecha, "descripcion": cc.Descripcion, "valor": cc.Valor, "estado": cc.Estado, "fecha_vencimiento": cc.FechaVencimiento, "fecha_pago": cc.FechaPago, "transaccion_id": cc.TransaccionID, "notas": cc.Notas, }).Error } func DeleteCuentaCobro(id uint) error { return app.Http.Database.DB.Delete(&CuentaCobro{}, id).Error } // ============================================================================= // ─── CRUD: CuentaPagar ────────────────────────────────────────────────────── // ============================================================================= func GetAllCuentasPagar(limit, offset int, search string, estado string) ([]CuentaPagar, int64, error) { var items []CuentaPagar var total int64 db := app.Http.Database.DB.Model(&CuentaPagar{}).Preload("Entidad").Preload("Transaccion") if search != "" { db = db.Joins("JOIN contab_entidades ON contab_entidades.id = contab_cuentas_pagar.entidad_id"). Where("contab_entidades.nombre ILIKE ? OR contab_cuentas_pagar.descripcion ILIKE ?", "%"+search+"%", "%"+search+"%") } if estado != "" { db = db.Where("contab_cuentas_pagar.estado = ?", estado) } if err := db.Count(&total).Error; err != nil { return nil, 0, err } if err := db.Order("contab_cuentas_pagar.fecha DESC").Limit(limit).Offset(offset).Find(&items).Error; err != nil { return nil, 0, err } return items, total, nil } func CreateCuentaPagar(cp *CuentaPagar) error { return app.Http.Database.DB.Create(cp).Error } func UpdateCuentaPagar(cp *CuentaPagar) error { return app.Http.Database.DB.Model(&CuentaPagar{}).Where("id = ?", cp.ID).Updates(map[string]interface{}{ "entidad_id": cp.EntidadID, "fecha": cp.Fecha, "descripcion": cp.Descripcion, "valor": cp.Valor, "vencimiento": cp.Vencimiento, "estado": cp.Estado, "fecha_pago": cp.FechaPago, "transaccion_id": cp.TransaccionID, "notas": cp.Notas, }).Error } func DeleteCuentaPagar(id uint) error { return app.Http.Database.DB.Delete(&CuentaPagar{}, id).Error } // ============================================================================= // ─── Consolidado ──────────────────────────────────────────────────────────── // ============================================================================= func GetConsolidadoMensual(mes, anio int) (*ConsolidadoMensual, error) { var c ConsolidadoMensual err := app.Http.Database.DB.Where("mes = ? AND anio = ?", mes, anio).First(&c).Error if err != nil { return nil, err } return &c, nil } func CalcularYGuardarConsolidado(mes, anio int) (*ConsolidadoMensual, error) { type SumRow struct { Total float64 } var ingresos SumRow var egresos SumRow app.Http.Database.DB.Model(&Transaccion{}). Select("COALESCE(SUM(valor),0) as total"). Where("tipo = ? AND EXTRACT(MONTH FROM fecha) = ? AND EXTRACT(YEAR FROM fecha) = ?", "ingreso", mes, anio). Scan(&ingresos) app.Http.Database.DB.Model(&Transaccion{}). Select("COALESCE(SUM(valor),0) as total"). Where("tipo = ? AND EXTRACT(MONTH FROM fecha) = ? AND EXTRACT(YEAR FROM fecha) = ?", "egreso", mes, anio). Scan(&egresos) resultado := ingresos.Total - egresos.Total c := &ConsolidadoMensual{ Anio: anio, Mes: mes, TotalIngresos: ingresos.Total, TotalEgresos: egresos.Total, TotalRetenciones: 0, Resultado: resultado, } var existing ConsolidadoMensual err := app.Http.Database.DB.Where("mes = ? AND anio = ?", mes, anio).First(&existing).Error if err == nil { c.ID = existing.ID app.Http.Database.DB.Model(&ConsolidadoMensual{}).Where("id = ?", c.ID).Updates(map[string]interface{}{ "total_ingresos": c.TotalIngresos, "total_egresos": c.TotalEgresos, "total_retenciones": c.TotalRetenciones, "resultado": c.Resultado, }) } else { app.Http.Database.DB.Create(c) } return c, nil } func ListConsolidados(anio int) ([]ConsolidadoMensual, error) { var items []ConsolidadoMensual db := app.Http.Database.DB.Model(&ConsolidadoMensual{}).Order("anio DESC, mes DESC") if anio > 0 { db = db.Where("anio = ?", anio) } if err := db.Find(&items).Error; err != nil { return nil, err } return items, nil } // ─── Datos para dashboard ──────────────────────────────────────────────────── type DashboardData struct { Mes int `json:"mes"` Anio int `json:"anio"` TotalIngresos float64 `json:"total_ingresos"` TotalEgresos float64 `json:"total_egresos"` Resultado float64 `json:"resultado"` CantTransacciones int64 `json:"cant_transacciones"` PendientesCobro float64 `json:"pendientes_cobro"` PendientesPago float64 `json:"pendientes_pago"` Transacciones []Transaccion `json:"transacciones"` } func GetDashboardData(mes, anio int) (*DashboardData, error) { d := &DashboardData{Mes: mes, Anio: anio} app.Http.Database.DB.Model(&Transaccion{}). Select("COALESCE(SUM(valor),0) as total"). Where("tipo = ? AND EXTRACT(MONTH FROM fecha) = ? AND EXTRACT(YEAR FROM fecha) = ?", "ingreso", mes, anio). Scan(&d.TotalIngresos) app.Http.Database.DB.Model(&Transaccion{}). Select("COALESCE(SUM(valor),0) as total"). Where("tipo = ? AND EXTRACT(MONTH FROM fecha) = ? AND EXTRACT(YEAR FROM fecha) = ?", "egreso", mes, anio). Scan(&d.TotalEgresos) d.Resultado = d.TotalIngresos - d.TotalEgresos app.Http.Database.DB.Model(&Transaccion{}). Where("EXTRACT(MONTH FROM fecha) = ? AND EXTRACT(YEAR FROM fecha) = ?", mes, anio). Count(&d.CantTransacciones) app.Http.Database.DB.Model(&CuentaCobro{}). Select("COALESCE(SUM(valor),0) as total"). Where("estado = ?", "pendiente"). Scan(&d.PendientesCobro) app.Http.Database.DB.Model(&CuentaPagar{}). Select("COALESCE(SUM(valor),0) as total"). Where("estado = ?", "pendiente"). Scan(&d.PendientesPago) app.Http.Database.DB.Model(&Transaccion{}). Preload("Cuenta").Preload("Entidad"). Where("EXTRACT(MONTH FROM fecha) = ? AND EXTRACT(YEAR FROM fecha) = ?", mes, anio). Order("fecha DESC"). Limit(10). Find(&d.Transacciones) return d, nil } func SeedContabilidad() { cuentas := []Cuenta{ {Codigo: "ING-FAC", Nombre: "Facturación", Tipo: "ingreso", Color: "#22c55e"}, {Codigo: "ING-OTR", Nombre: "Otros ingresos", Tipo: "ingreso", Color: "#16a34a"}, {Codigo: "EGR-HOS", Nombre: "Hosting/Servidores", Tipo: "egreso", Color: "#ef4444"}, {Codigo: "EGR-DOM", Nombre: "Dominios", Tipo: "egreso", Color: "#dc2626"}, {Codigo: "EGR-SRV", Nombre: "Servicios", Tipo: "egreso", Color: "#f97316"}, {Codigo: "EGR-GRAL", Nombre: "Gastos generales", Tipo: "egreso", Color: "#eab308"}, {Codigo: "EGR-NOM", Nombre: "Nómina", Tipo: "egreso", Color: "#a855f7"}, {Codigo: "EGR-IMPU", Nombre: "Impuestos", Tipo: "egreso", Color: "#6366f1"}, {Codigo: "EGR-MKT", Nombre: "Marketing", Tipo: "egreso", Color: "#ec4899"}, } for _, c := range cuentas { var existing Cuenta if app.Http.Database.DB.Where("codigo = ?", c.Codigo).First(&existing).Error != nil { app.Http.Database.DB.Create(&c) } } entidades := []Entidad{ {Nombre: "ANDREMER", Tipo: "cliente"}, {Nombre: "GIAF SAS", Tipo: "cliente"}, {Nombre: "TECZONE", Tipo: "cliente"}, {Nombre: "DOCUXER", Tipo: "cliente"}, {Nombre: "FELIPE", Tipo: "cliente"}, {Nombre: "NATALIA", Tipo: "cliente"}, } for _, e := range entidades { var existing Entidad if app.Http.Database.DB.Where("nombre = ?", e.Nombre).First(&existing).Error != nil { app.Http.Database.DB.Create(&e) } } }