conflicto resuelto

This commit is contained in:
aldair.guzman 2025-12-12 11:16:15 -04:00
commit 96e80a1948
8 changed files with 93 additions and 36 deletions

18
.env
View File

@ -1,6 +1,6 @@
#Configuración de la base de datos
DB_HOST=localhost
DB_PORT=5434
DB_HOST=db
DB_PORT=5432
DB_USER=admin
DB_PASSWORD=admin
DB_NAME=gotestdb
@ -8,7 +8,7 @@ DB_MAX_OPEN_CONNS=25
DB_MAX_IDLE_CONNS=10
DB_CONN_MAX_LIFETIME=30m
#Redis
REDIS_HOST=localhost
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=TuPasswordSegura123
REDIS_DB=0
@ -16,18 +16,18 @@ REDIS_TTL=10m
REDIS_SUBSCRIBE=cron:reload
#Log
LOG_FILE_PATH=logs/syncronizador.log
LOG_LEVEL=debug
LOG_LEVEL=info
LOG_MAX_SIZE=10
LOG_MAX_BACKUPS=7
LOG_MAX_AGE=30
LOG_COMPRESS=true
#Cliente Rest
TIMEOUT_SECONDS= 30
TIMEOUT_SECONDS=30
RETRY_COUNT=3
TLS_SKIP_VERIFY=true
LOG_REQUESTS=false
ENABLE_DEBUG= false
ENABLE_DEBUG=false
WHERE_UNITS_BUSINESS=company_name = @company_name AND company_db = @company_db AND status = 'A'
@ -41,13 +41,13 @@ ENCRYPTION_KEY=12345678901234567890123456789012
AUTH_ENDPOINT= http://localhost:8085/auth/headers
AUTH_AUTORIZATION= 'Basic d29ya2VyLXJlbmRpY2lvbjpwcnVlYmE='
AUTH_ENDPOINT=http://authsvc:8080/auth/headers
AUTH_AUTORIZATION='Basic d29ya2VyLXJlbmRpY2lvbjpwcnVlYmE='
AUTH_METHOD=POST
TM_HEADER_ORIGIN=https://azure-function.timemanagerweb.com
TM_HEADER_TENANT_NAME=pruebas-dos
TM_HEADER_USER_AGENT='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36'
TM_HEADER_USER_AGENT=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
# Configuración de correo SMTP

View File

@ -7,6 +7,7 @@ import (
"os"
"os/signal"
"syscall"
"time"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/tuusuario/go-sync-service/internal/config"
@ -16,25 +17,28 @@ import (
)
func main() {
// Configurar zona horaria por defecto del proceso
initTimezone()
// Cargar configuración
conf, err := config.LoadConfig()
if err != nil {
fmt.Println("Error cargando configuración:", err)
fmt.Println("Error cargando configuración:", err)
os.Exit(1)
}
// Inicializar logger con configuración
config.InitLogger(conf)
config.Log.Info("🚀 Iniciando servicio: go-sync-service")
config.Log.Info("Iniciando servicio: go-sync-service")
emailService := email.NewSMTPEmailService(conf)
// Conexión a Redis
redisClient := config.GetRedisClient(conf)
if err := redisClient.Ping(context.Background()).Err(); err != nil {
config.Log.Fatalf("Redis no disponible: %v", err)
config.Log.Fatalf("Redis no disponible: %v", err)
}
config.Log.Info("Redis conectado")
config.Log.Info("Redis conectado")
// Crear proveedor de configuración desde Redis
redisManager := config.NewRedisManager(redisClient)
@ -43,14 +47,14 @@ func main() {
// Conexión a Base de Datos
database := config.GetDatabaseConnection(conf)
if database == nil {
config.Log.Fatal("No se pudo establecer la conexión con la base de datos.")
config.Log.Fatal("No se pudo establecer la conexión con la base de datos.")
}
config.Log.Info("Conexión a base de datos establecida")
config.Log.Info("Conexión a base de datos establecida")
scheduler.Start(context.Background(), redisClient, redisConfigProvider, database, *emailService)
config.Log.Info("Scheduler en ejecución y escuchando recargas")
config.Log.Info("Scheduler en ejecución y escuchando recargas")
//metrics Grafana
// Metrics Grafana/Prometheus
metrics.Register()
startMetricsServer()
@ -59,13 +63,24 @@ func main() {
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
<-stop
config.Log.Info("🛑 Señal de apagado recibida, cerrando servicio...")
config.Log.Info("Señal de apagado recibida, cerrando servicio...")
}
func initTimezone() {
loc, err := time.LoadLocation("America/La_Paz")
if err != nil {
// Si falla la carga, mantenemos la zona por defecto del entorno
// y solo registramos el problema en stdout para no depender del logger aún.
fmt.Println("No se pudo cargar la zona horaria America/La_Paz:", err)
return
}
time.Local = loc
}
func startMetricsServer() {
go func() {
http.Handle("/metrics", promhttp.Handler())
config.Log.Info("📊 Servidor de métricas en :9100/metrics")
http.ListenAndServe(":9100", nil)
config.Log.Info("Servidor de métricas en :9100/metrics")
_ = http.ListenAndServe(":9100", nil)
}()
}

View File

@ -1,6 +1,10 @@
services:
syncronizador:
container_name: rendicion_gasto_syncronizador
environment:
- TZ=America/La_Paz
- LANG=es_BO.UTF-8
- LC_ALL=es_BO.UTF-8
build:
context: .
dockerfile: Dockerfile
@ -11,7 +15,12 @@ services:
- ./logs:/app/logs
networks:
- network_emba
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
compress: "true"
networks:
network_emba:
external: true

View File

@ -17,9 +17,34 @@ import (
// Log instancia global del logger
var Log = logrus.New()
type LocalTimeHook struct {
loc *time.Location
}
func (h LocalTimeHook) Levels() []logrus.Level {
return logrus.AllLevels
}
func (h LocalTimeHook) Fire(e *logrus.Entry) error {
if h.loc != nil {
e.Time = e.Time.In(h.loc)
}
return nil
}
func InitLogger(cfg *Config) {
// =========================
// Zona horaria Bolivia
// =========================
loc, err := time.LoadLocation("America/La_Paz")
if err != nil {
// Fallback robusto para contenedores sin tzdata
loc = time.FixedZone("America/La_Paz", -4*60*60)
}
time.Local = loc
// Hook para forzar timestamps de Logrus a hora Bolivia
Log.AddHook(LocalTimeHook{loc: loc})
// Configurar rotación de logs con Lumberjack
rotator := &lumberjack.Logger{
Filename: cfg.LogFilePath, // Archivo de logs
@ -56,7 +81,7 @@ func InitLogger(cfg *Config) {
//elastic
if cfg.ElasticEnabled {
Log.Debug("✅ Elasticsearch enabled")
Log.Info("✅ Elasticsearch enabled")
es, err := elasticsearch.NewClient(elasticsearch.Config{
Addresses: []string{cfg.ElasticURL},
})

View File

@ -64,7 +64,7 @@ func (g *GormDatabase) SyncRows(persistencia dto.Persistencia, rawData *[]map[st
// Procesar lote
if len(batch) == batchSize || i == len(*rawData)-1 {
config.Log.Debugf(logPrefix+" Procesando batch de %d registros", len(batch))
config.Log.Println(logPrefix+" Procesando batch de %d registros", len(batch))
if len(persistencia.UpdateBy) > 0 {
// Updates con múltiples campos
@ -81,7 +81,7 @@ func (g *GormDatabase) SyncRows(persistencia dto.Persistencia, rawData *[]map[st
whereValues = append(whereValues, val)
}
if len(whereParts) < len(persistencia.UpdateBy) {
config.Log.Warnf("⚠️ Registro incompleto para update (faltan claves): %+v", row)
config.Log.Debugf("⚠️ Registro incompleto para update (faltan claves): %+v", row)
continue
}
@ -108,10 +108,10 @@ func (g *GormDatabase) SyncRows(persistencia dto.Persistencia, rawData *[]map[st
config.Log.Errorf("%s ❌ Error en update: %v", logPrefix, res.Error)
return res.Error
}
if res.RowsAffected == 0 {
config.Log.Warnf("%s ⚠️ Ninguna fila afectada con campos: %v valores: %v",
/* if res.RowsAffected == 0 {
config.Log.Debugf("%s ⚠️ Ninguna fila afectada con campos: %v valores: %v",
logPrefix, strings.Join(whereParts, " AND "), printWhereValues(whereValues))
}
} */
}
} else {
// Inserts con conflicto por PK (UPSERT)
@ -173,17 +173,17 @@ func (g *GormDatabase) GetCredencialesFromTemplate(whereTemplate string, variabl
query := whereTemplate
var args []interface{}
config.Log.Debugf("🔎 Variables recibidas:")
config.Log.Info("🔎 Variables recibidas:")
for k, v := range variables {
placeholder := "@" + k
query = strings.ReplaceAll(query, placeholder, "?")
args = append(args, v)
config.Log.Debugf(" %s = %v", k, v)
config.Log.Infof(" %s = %v", k, v)
}
config.Log.Debugf("📝 Consulta final construida:")
config.Log.Debugf(" Query: %s", query)
config.Log.Debugf(" Args: %v", args)
config.Log.Info("📝 Consulta final construida:")
config.Log.Infof(" Query: %s", query)
config.Log.Infof(" Args: %v", args)
err := g.db.Where(query, args...).First(&cred).Error
return &cred, err

View File

@ -155,13 +155,13 @@ func prepareAuthBody(auth dto.ServiceConfig, cred *model.CredencialesSAP, logPre
}
if auth.GQL {
config.Log.Debugf("%v 🧠 Preparando auth para GraphQL", logPrefix)
config.Log.Infof("%v 🧠 Preparando auth para GraphQL", logPrefix)
auth.Rest.Body = map[string]string{
"username": cred.UserName,
"password": cred.Password,
}
} else {
config.Log.Debugf("%v🧠 Preparando auth para REST", logPrefix)
config.Log.Infof("%v🧠 Preparando auth para REST", logPrefix)
auth.Rest.Body = map[string]string{
"CompanyDB": cred.CompanyDB,
"UserName": cred.UserName,

View File

@ -3,11 +3,12 @@ package fetcher
import (
"encoding/json"
"fmt"
"github.com/tuusuario/go-sync-service/internal/email"
"strconv"
"strings"
"time"
"github.com/tuusuario/go-sync-service/internal/email"
"github.com/tuusuario/go-sync-service/internal/config"
"github.com/tuusuario/go-sync-service/internal/db"
"github.com/tuusuario/go-sync-service/internal/domain/dto"
@ -122,6 +123,9 @@ func FetchAllPaginatedManual[T any](host string, service dto.ServiceConfig, logP
return nil, fmt.Errorf("%s ❌ error en la petición: %w", logPrefix, err)
}
// DEBUG: imprimir lo que devuelve el servicio
config.Log.Debugf("%s Body recibido (REST paginado): %s", logPrefix, string(resp.Body()))
var result struct {
Value []T `json:"value"`
}
@ -206,6 +210,10 @@ func FetchAllPaginatedManual[T any](host string, service dto.ServiceConfig, logP
if err != nil {
return nil, fmt.Errorf("%s ❌ error en la petición: %w", logPrefix, err)
}
// DEBUG: imprimir lo que devuelve el servicio sin paginación
config.Log.Debugf("%s Body recibido (REST sin paginación): %s", logPrefix, string(resp.Body()))
var result struct {
Value []T `json:"value"`
}

View File

@ -17,7 +17,7 @@ func CargarDesdeRedis[T any](cfg ports.RedisConfigProvider, clave string) (*T, e
return nil, fmt.Errorf("error al obtener clave [%s] de redis: %w", clave, err)
}
config.Log.Debugf("🔑 Clave [%s] obtenida de Redis: %s", clave, data)
config.Log.Infof("🔑 Clave [%s] obtenida de Redis: %s", clave, data)
var result T
if err := json.Unmarshal([]byte(data), &result); err != nil {
config.Log.Errorf("❌ error al parsear JSON [%s]: %s", clave, err)