package internal import ( "errors" "fmt" "regexp" "strconv" "time" ) var ( ErrEmptyNIF = errors.New("NIF cannot be empty") ErrInvalidNIF = errors.New("invalid NIF format") ErrEmptyNumSerie = errors.New("number series cannot be empty") ErrInvalidFecha = errors.New("invalid date format, expected dd-mm-yyyy") ErrInvalidTipo = errors.New("invalid invoice type") ErrInvalidImporte = errors.New("invalid amount format") ErrEmptyNombre = errors.New("business name cannot be empty") ErrInvalidIVACampo = errors.New("invalid IVA field: must be numeric") ) // Empresa/CIF: letra + 7 dígitos + letra o dígito (A1234567B, B12345678) // Autónomo/NIF: 8 dígitos + letra (53950250R) var nifRegex = regexp.MustCompile(`^([A-Z]\d{7}[A-Z0-9]|\d{8}[A-Z])$`) var tipoFacturaValidos = map[string]bool{ "F1": true, "F2": true, "R1": true, "R2": true, "R3": true, "R4": true, "R5": true, } type InvoiceInput struct { Tipo string `json:"tipo"` Factura FacturaInput `json:"factura"` Sistema SistemaInput `json:"sistema"` } type FacturaInput struct { EmisorNIF string `json:"emisor_nif"` EmisorNombre string `json:"emisor_nombre"` NumSerie string `json:"num_serie"` FechaExpedicion string `json:"fecha_expedicion"` TipoFactura string `json:"tipo_factura"` Descripcion string `json:"descripcion"` Destinatario *DestinatarioInput `json:"destinatario,omitempty"` IVA []IVAInput `json:"iva"` ImporteTotal float64 `json:"importe_total"` } type DestinatarioInput struct { Nombre string `json:"nombre"` NIF string `json:"nif"` } type IVAInput struct { Base float64 `json:"base"` Cuota float64 `json:"cuota"` Tipo float64 `json:"tipo"` } type SistemaInput struct { Nombre string `json:"nombre"` NIFProveedor string `json:"nif_proveedor"` Version string `json:"version"` } type ValidationError struct { Field string `json:"field"` Message string `json:"message"` } func (e *ValidationError) Error() string { return fmt.Sprintf("%s: %s", e.Field, e.Message) } func ValidateInvoiceInput(in *InvoiceInput) []error { var errs []error if in.Tipo == "" { errs = append(errs, &ValidationError{"tipo", "operation type is required"}) } else if in.Tipo != "alta" && in.Tipo != "anulacion" { errs = append(errs, &ValidationError{"tipo", "must be 'alta' or 'anulacion'"}) } if in.Factura.EmisorNIF == "" { errs = append(errs, &ValidationError{"factura.emisor_nif", "cannot be empty"}) } else if !isValidNIF(in.Factura.EmisorNIF) { errs = append(errs, &ValidationError{"factura.emisor_nif", "invalid NIF format"}) } if in.Factura.NumSerie == "" { errs = append(errs, &ValidationError{"factura.num_serie", "cannot be empty"}) } if in.Factura.FechaExpedicion == "" { errs = append(errs, &ValidationError{"factura.fecha_expedicion", "cannot be empty"}) } else if !isValidDate(in.Factura.FechaExpedicion) { errs = append(errs, &ValidationError{"factura.fecha_expedicion", "invalid format, expected dd-mm-yyyy"}) } if in.Factura.TipoFactura == "" { errs = append(errs, &ValidationError{"factura.tipo_factura", "cannot be empty"}) } else if !tipoFacturaValidos[in.Factura.TipoFactura] { errs = append(errs, &ValidationError{"factura.tipo_factura", "invalid invoice type"}) } if len(in.Factura.IVA) == 0 { errs = append(errs, &ValidationError{"factura.iva", "at least one IVA entry is required"}) } for i, iva := range in.Factura.IVA { if iva.Base < 0 { errs = append(errs, &ValidationError{fmt.Sprintf("factura.iva[%d].base", i), "must be positive"}) } if iva.Cuota < 0 { errs = append(errs, &ValidationError{fmt.Sprintf("factura.iva[%d].cuota", i), "must be positive"}) } if iva.Tipo < 0 { errs = append(errs, &ValidationError{fmt.Sprintf("factura.iva[%d].tipo", i), "must be positive"}) } } if in.Factura.ImporteTotal <= 0 { errs = append(errs, &ValidationError{"factura.importe_total", "must be greater than 0"}) } // Destinatario es opcional, no se valida NIF _ = in.Factura.Destinatario if in.Sistema.Nombre == "" { errs = append(errs, &ValidationError{"sistema.nombre", "cannot be empty"}) } if in.Sistema.NIFProveedor == "" { errs = append(errs, &ValidationError{"sistema.nif_proveedor", "cannot be empty"}) } if in.Sistema.Version == "" { errs = append(errs, &ValidationError{"sistema.version", "cannot be empty"}) } return errs } func isValidNIF(nif string) bool { if len(nif) != 9 { return false } return nifRegex.MatchString(nif) } func isValidDate(date string) bool { _, err := time.Parse("02-01-2006", date) return err == nil } func ParseFloat(s string) (float64, error) { if s == "" { return 0, ErrInvalidImporte } return strconv.ParseFloat(s, 64) }