independencia del provider
This commit is contained in:
@@ -163,6 +163,10 @@ type eaCustomer struct {
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
type eaProvider struct {
|
||||
ID flexInt `json:"id"`
|
||||
}
|
||||
|
||||
type eaAppointment struct {
|
||||
Start string `json:"start"`
|
||||
End string `json:"end"`
|
||||
@@ -181,7 +185,6 @@ type bookRequest struct {
|
||||
Email string `json:"email"`
|
||||
Telefono string `json:"telefono"`
|
||||
Mensaje string `json:"mensaje"`
|
||||
ProviderID int `json:"providerId"`
|
||||
ServiceIDs []int `json:"serviceIds"`
|
||||
Date string `json:"date"` // YYYY-MM-DD
|
||||
Time string `json:"time"` // HH:MM
|
||||
@@ -195,8 +198,9 @@ var (
|
||||
// handleBook orquesta la reserva contra EasyAppointments:
|
||||
// 1. valida la entrada,
|
||||
// 2. lee los servicios de EA (fuente de verdad de nombre/duración),
|
||||
// 3. busca o crea el cliente por email,
|
||||
// 4. crea UNA cita con el servicio de mayor duración y el bloque total en notas.
|
||||
// 3. resuelve el provider (server-side, ver providerID),
|
||||
// 4. busca o crea el cliente por email,
|
||||
// 5. crea UNA cita con el servicio de mayor duración y el bloque total en notas.
|
||||
func handleBook(ea *eaClient) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
@@ -216,7 +220,7 @@ func handleBook(ea *eaClient) http.HandlerFunc {
|
||||
req.Mensaje = strings.TrimSpace(req.Mensaje)
|
||||
|
||||
if req.Nombre == "" || req.Apellidos == "" || req.Email == "" || req.Telefono == "" ||
|
||||
len(req.ServiceIDs) == 0 || req.ProviderID <= 0 ||
|
||||
len(req.ServiceIDs) == 0 ||
|
||||
!reDate.MatchString(req.Date) || !reTime.MatchString(req.Time) {
|
||||
writeJSONError(w, http.StatusBadRequest, "datos_incompletos")
|
||||
return
|
||||
@@ -284,7 +288,15 @@ func handleBook(ea *eaClient) http.HandlerFunc {
|
||||
}
|
||||
notes := strings.Join(notesLines, "\n")
|
||||
|
||||
// 5. cliente: buscar por email o crear (en EA).
|
||||
// 5. provider resuelto server-side: el navegador no elige contra quién reserva.
|
||||
providerID, err := ea.providerID()
|
||||
if err != nil {
|
||||
log.Printf("book: error resolviendo provider: %v", err)
|
||||
writeJSONError(w, http.StatusBadGateway, "proveedor_no_disponible")
|
||||
return
|
||||
}
|
||||
|
||||
// 6. cliente: buscar por email o crear (en EA).
|
||||
customerID, err := ea.findOrCreateCustomer(req)
|
||||
if err != nil {
|
||||
log.Printf("book: error con cliente: %v", err)
|
||||
@@ -292,12 +304,12 @@ func handleBook(ea *eaClient) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
// 6. crear la cita (en EA).
|
||||
// 7. crear la cita (en EA).
|
||||
appt := eaAppointment{
|
||||
Start: start.Format(layout),
|
||||
End: end.Format(layout),
|
||||
ServiceID: int(primary.ID),
|
||||
ProviderID: req.ProviderID,
|
||||
ProviderID: providerID,
|
||||
CustomerID: customerID,
|
||||
Notes: notes,
|
||||
Status: "booked",
|
||||
@@ -314,6 +326,50 @@ func handleBook(ea *eaClient) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// providerID resuelve el id del provider contra el que se reserva. Con
|
||||
// EA_PROVIDER_ID definido se usa ese valor; si no, se pregunta a EA y se toma
|
||||
// el primero (el salón tiene un único provider). Así el id no va hardcodeado
|
||||
// y cada entorno (desarrollo, producción) resuelve el suyo.
|
||||
func (c *eaClient) providerID() (int, error) {
|
||||
if v := os.Getenv("EA_PROVIDER_ID"); v != "" {
|
||||
n, err := strconv.Atoi(v)
|
||||
if err != nil || n <= 0 {
|
||||
return 0, fmt.Errorf("EA_PROVIDER_ID inválido: %q", v)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
var provs []eaProvider
|
||||
if err := c.do(http.MethodGet, "/providers", nil, &provs); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(provs) == 0 {
|
||||
return 0, fmt.Errorf("EA no tiene ningún provider")
|
||||
}
|
||||
if len(provs) > 1 {
|
||||
log.Printf("aviso: EA tiene %d providers; se usa el primero (id %d). Fija EA_PROVIDER_ID para elegir otro.", len(provs), int(provs[0].ID))
|
||||
}
|
||||
return int(provs[0].ID), nil
|
||||
}
|
||||
|
||||
// handleConfig expone al frontend la configuración mínima que necesita para
|
||||
// consultar disponibilidad por el proxy: solo el id del provider.
|
||||
func handleConfig(ea *eaClient) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
writeJSONError(w, http.StatusMethodNotAllowed, "method_not_allowed")
|
||||
return
|
||||
}
|
||||
id, err := ea.providerID()
|
||||
if err != nil {
|
||||
log.Printf("config: error resolviendo provider: %v", err)
|
||||
writeJSONError(w, http.StatusBadGateway, "proveedor_no_disponible")
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, `{"providerId": %d}`, id)
|
||||
}
|
||||
}
|
||||
|
||||
// findOrCreateCustomer busca un cliente por email en EA y, si no existe, lo crea.
|
||||
func (c *eaClient) findOrCreateCustomer(req bookRequest) (int, error) {
|
||||
var found []eaCustomer
|
||||
@@ -408,6 +464,10 @@ func main() {
|
||||
// Reservas: orquestadas server-side contra EasyAppointments.
|
||||
http.HandleFunc("/api/book", handleBook(ea))
|
||||
|
||||
// Config para el frontend: el id del provider se resuelve aquí (EA o
|
||||
// EA_PROVIDER_ID), nunca va hardcodeado en el HTML.
|
||||
http.HandleFunc("/api/config", handleConfig(ea))
|
||||
|
||||
http.Handle("/", http.FileServer(http.FS(staticFS)))
|
||||
|
||||
addr := fmt.Sprintf(":%d", *port)
|
||||
|
||||
Reference in New Issue
Block a user