package internal import ( "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "os" "path/filepath" "regexp" "strings" "time" ) type HashService struct { lastRecord *LastRecord } type LastRecord struct { EmisorNIF string NumSerie string Fecha time.Time Huella string FechaGen time.Time } func NewHashService() *HashService { return &HashService{} } func (s *HashService) SetLastRecord(r *LastRecord) { s.lastRecord = r } func (s *HashService) GetLastRecord() *LastRecord { return s.lastRecord } func (s *HashService) CalculateHash(data *InvoiceData, previousHash string) string { fechaGen := data.FechaGen.Format(time.RFC3339) fields := fmt.Sprintf("%s|%s|%s|%s|%.2f|%.2f|%s|%s", data.EmisorNIF, data.NumSerie, data.Fecha.Format("02-01-2006"), data.TipoFactura, data.CuotaTotal, data.ImporteTotal, previousHash, fechaGen, ) hash := sha256.Sum256([]byte(fields)) return strings.ToUpper(hex.EncodeToString(hash[:])) } func (s *HashService) IsFirstRecord() bool { return s.lastRecord == nil } func (s *HashService) GetPreviousHash() string { if s.lastRecord == nil { return "" } return s.lastRecord.Huella } type LastRecordStorage interface { GetLastRecord(emisorNIF string) (*LastRecord, error) SaveLastRecord(r *LastRecord) error } type FileLastRecordStorage struct { basePath string } func NewFileLastRecordStorage(basePath string) *FileLastRecordStorage { return &FileLastRecordStorage{basePath: basePath} } func (s *FileLastRecordStorage) GetLastRecord(emisorNIF string) (*LastRecord, error) { if s.basePath == "" { s.basePath = "./data/" } filePath := filepath.Join(s.basePath, sanitizeFilename(emisorNIF)+".json") data, err := os.ReadFile(filePath) if err != nil { if os.IsNotExist(err) { return nil, nil } return nil, fmt.Errorf("reading last record: %w", err) } var record LastRecord if err := json.Unmarshal(data, &record); err != nil { return nil, fmt.Errorf("unmarshaling last record: %w", err) } return &record, nil } func (s *FileLastRecordStorage) SaveLastRecord(r *LastRecord) error { if s.basePath == "" { s.basePath = "./data/" } if err := os.MkdirAll(s.basePath, 0750); err != nil { return fmt.Errorf("creating directory: %w", err) } filePath := filepath.Join(s.basePath, sanitizeFilename(r.EmisorNIF)+".json") data, err := json.Marshal(r) if err != nil { return fmt.Errorf("marshaling last record: %w", err) } if err := os.WriteFile(filePath, data, 0640); err != nil { return fmt.Errorf("writing last record: %w", err) } return nil } func sanitizeFilename(name string) string { re := regexp.MustCompile(`[^a-zA-Z0-9]`) return re.ReplaceAllString(name, "_") }