feat: automatización de cotizaciones, contratos, actas y cuentas de cobro con IA

Implementa las 4 fases de la especificación de automatización: módulo de
plantillas/tarifas editable por el equipo, generación de PDF (HTML+JS vía
Chrome headless) para cotizaciones/contratos/arquitecturas/cuentas de cobro,
chat propio en el dashboard reutilizando el mismo motor y tools del bot de
Telegram, y nuevas tools del agente para crear estos documentos end-to-end.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro GD
2026-08-03 00:45:58 +00:00
co-authored by Claude Sonnet 5
parent 772a4a5656
commit 516220a8a1
32 changed files with 2272 additions and 132 deletions
+93 -66
View File
@@ -61,19 +61,19 @@ func (Transaccion) TableName() string { return "contab_transacciones" }
type CuentaCobro struct {
gorm.Model
ClienteID uint `json:"cliente_id" gorm:"column:cliente_id;index;not null"`
Cliente Cliente `json:"cliente" gorm:"foreignKey:ClienteID"`
EntidadID *uint `json:"entidad_id" gorm:"column:entidad_id;index"`
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"`
ClienteID uint `json:"cliente_id" gorm:"column:cliente_id;index;not null"`
Cliente Cliente `json:"cliente" gorm:"foreignKey:ClienteID"`
EntidadID *uint `json:"entidad_id" gorm:"column:entidad_id;index"`
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" }
@@ -82,17 +82,17 @@ func (CuentaCobro) TableName() string { return "contab_cuentas_cobro" }
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"`
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" }
@@ -101,12 +101,12 @@ func (CuentaPagar) TableName() string { return "contab_cuentas_pagar" }
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"`
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"`
Resultado float64 `json:"resultado" gorm:"column:resultado;default:0"`
}
func (ConsolidadoMensual) TableName() string { return "contab_consolidado" }
@@ -307,7 +307,11 @@ func SeedBalanceData() {
}
// ─── FACTURAS → Transacciones (ingresos) ────────────────────────────────
facturas := []struct{ factura float64; valor float64; fecha string }{
facturas := []struct {
factura float64
valor float64
fecha string
}{
{54, 450000, "2026-01-01 00:00:00"},
{55, 9000000, "2026-01-01 00:00:00"},
{56, 119000, "2026-01-01 00:00:00"},
@@ -347,7 +351,7 @@ func SeedBalanceData() {
if db.Where("descripcion = ?", "Ajuste/NC ene 2026").First(&existing).Error != nil {
db.Create(&Transaccion{
Fecha: parseDate("2026-01-01 00:00:00"),
Tipo: "egreso", Descripcion: "Ajuste/NC ene 2026",
Tipo: "egreso", Descripcion: "Ajuste/NC ene 2026",
Valor: 130000, CuentaID: cuentaIng, Estado: "registrada",
})
}
@@ -369,7 +373,11 @@ func SeedBalanceData() {
}
// ─── IVA → Transacciones (egresos) ──────────────────────────────────────
ivas := []struct{ valor float64; entidad string; desc string }{
ivas := []struct {
valor float64
entidad string
desc string
}{
{285000, "DOCUXER", "IVA Fact #63"},
{503500, "DOCUXER", "IVA Fact #70"},
{19000, "GIAF SAS", "IVA Fact #56"},
@@ -383,7 +391,7 @@ func SeedBalanceData() {
eid := getEntidad(iv.entidad)
db.Create(&Transaccion{
Fecha: parseDate("2026-01-01 00:00:00"),
Tipo: "egreso", Descripcion: iv.desc,
Tipo: "egreso", Descripcion: iv.desc,
Valor: iv.valor, CuentaID: cuentaImp,
EntidadID: eid, Estado: "registrada",
})
@@ -391,7 +399,11 @@ func SeedBalanceData() {
}
// ─── RETENCION → Transacciones (egresos) ────────────────────────────────
rets := []struct{ valor float64; fecha string; estado string }{
rets := []struct {
valor float64
fecha string
estado string
}{
{195000, "2026-03-01 00:00:00", "pagado"},
{257000, "2026-02-01 00:00:00", "pagado"},
{431000, "2026-01-01 00:00:00", "pendiente"},
@@ -402,7 +414,7 @@ func SeedBalanceData() {
if db.Where("descripcion = ?", desc).First(&existing).Error != nil {
db.Create(&Transaccion{
Fecha: parseDate(r.fecha),
Tipo: "egreso", Descripcion: desc,
Tipo: "egreso", Descripcion: desc,
Valor: r.valor, CuentaID: cuentaImp,
Estado: "registrada",
})
@@ -410,7 +422,11 @@ func SeedBalanceData() {
}
// ─── CUENTAS DE COBRO (las que te pasan a ti) → CuentasPagar ────────────
cobros := []struct{ entidad string; valor float64; fecha string }{
cobros := []struct {
entidad string
valor float64
fecha string
}{
{"NATALIA", 2000000, "2026-01-01 00:00:00"},
{"FELIPE", 2000000, "2026-01-01 00:00:00"},
{"CONTADORA", 1780000, "2026-01-01 00:00:00"},
@@ -439,7 +455,10 @@ func SeedBalanceData() {
}
// ─── CONSOLIDADO POR MES ───────────────────────────────────────────────
consols := []struct{ mes int; ing, egre, resul float64 }{
consols := []struct {
mes int
ing, egre, resul float64
}{
{1, 12669000, 8467849, 4201151},
{2, 8470500, 4300000, 4170500},
{3, 2109000, 4300000, -2191000},
@@ -459,6 +478,14 @@ func SeedBalanceData() {
log.Println("[SEED] Balance data imported from BALANCE.numbers")
}
func GetCuentaCobroByID(id uint) (*CuentaCobro, error) {
var item CuentaCobro
if err := app.Http.Database.DB.Preload("Cliente").Preload("Entidad").First(&item, id).Error; err != nil {
return nil, err
}
return &item, nil
}
func CreateCuentaCobro(cc *CuentaCobro) error {
return app.Http.Database.DB.Create(cc).Error
}
@@ -565,12 +592,12 @@ func CalcularYGuardarConsolidado(mes, anio int) (*ConsolidadoMensual, error) {
resultado := ingresos.Total - egresos.Total
c := &ConsolidadoMensual{
Anio: anio,
Mes: mes,
TotalIngresos: ingresos.Total,
TotalEgresos: egresos.Total,
Anio: anio,
Mes: mes,
TotalIngresos: ingresos.Total,
TotalEgresos: egresos.Total,
TotalRetenciones: 0,
Resultado: resultado,
Resultado: resultado,
}
var existing ConsolidadoMensual
@@ -605,15 +632,15 @@ func ListConsolidados(anio int) ([]ConsolidadoMensual, error) {
// ─── 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"`
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) {
@@ -657,15 +684,15 @@ func GetDashboardData(mes, anio int) (*DashboardData, error) {
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: "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"},
{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
@@ -675,13 +702,13 @@ func SeedContabilidad() {
}
entidades := []Entidad{
{Nombre: "DOCUXER", Tipo: "cliente"},
{Nombre: "GIAF SAS", Tipo: "cliente"},
{Nombre: "TECZONE", Tipo: "proveedor"},
{Nombre: "FELIPE", Tipo: "proveedor"},
{Nombre: "NATALIA", Tipo: "proveedor"},
{Nombre: "CONTADORA", Tipo: "proveedor"},
{Nombre: "ANDREMER", Tipo: "proveedor"},
{Nombre: "DOCUXER", Tipo: "cliente"},
{Nombre: "GIAF SAS", Tipo: "cliente"},
{Nombre: "TECZONE", Tipo: "proveedor"},
{Nombre: "FELIPE", Tipo: "proveedor"},
{Nombre: "NATALIA", Tipo: "proveedor"},
{Nombre: "CONTADORA", Tipo: "proveedor"},
{Nombre: "ANDREMER", Tipo: "proveedor"},
}
for _, e := range entidades {
var existing Entidad