change to gorilla

This commit is contained in:
betology 2025-04-04 15:55:58 -06:00
parent 54970ae013
commit f0c7fc472a
9 changed files with 632 additions and 162 deletions

View file

@ -3,9 +3,10 @@ package main
import ( import (
"html/template" "html/template"
"log" "log"
"net/http"
"github.com/gin-gonic/gin"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/gorilla/mux"
"go_selva/internal/api" "go_selva/internal/api"
"go_selva/internal/db" "go_selva/internal/db"
@ -19,44 +20,48 @@ func main() {
} }
defer database.Close() defer database.Close()
// Initialize Gin router // Initialize Gorilla Mux router
router := gin.Default() router := mux.NewRouter()
// Register the safeHTML function // Create a template registry and register the safeHTML function
router.SetFuncMap(template.FuncMap{ funcMap := template.FuncMap{
"safeHTML": func(s string) template.HTML { "safeHTML": func(s string) template.HTML {
return template.HTML(s) return template.HTML(s)
}, },
}) }
tmpl := template.New("").Funcs(funcMap)
// Load HTML templates // Load HTML templates
router.LoadHTMLGlob("templates/*") tmpl, err = tmpl.ParseGlob("templates/*")
if err != nil {
// Serve static files (CSS) log.Fatalf("Failed to load HTML templates: %v", err)
router.Static("/static", "./static")
// Initialize API handlers
apiHandler := api.NewAPIHandler(database)
// Define API routes
apiGroup := router.Group("/nombres")
{
apiGroup.GET("/search", apiHandler.SearchNombres)
apiGroup.POST("", apiHandler.CreateNombre)
apiGroup.GET("", apiHandler.GetNombres)
apiGroup.GET("/:id", apiHandler.GetNombreByID)
apiGroup.PUT("/:id", apiHandler.UpdateNombre)
apiGroup.DELETE("/:id", apiHandler.DeleteNombre)
apiGroup.GET("/html/edit/:id", apiHandler.EditNombreHTML)
apiGroup.POST("/html/update/:id", apiHandler.UpdateNombreHTML)
} }
// Serve static files (CSS)
router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
// Initialize API handlers
apiHandler := api.NewAPIHandler(database, tmpl) // Pass the loaded templates
// Define API routes
apiGroup := router.PathPrefix("/nombres").Subrouter()
apiGroup.HandleFunc("/search", apiHandler.SearchNombres).Methods("GET")
apiGroup.HandleFunc("", apiHandler.CreateNombre).Methods("POST")
apiGroup.HandleFunc("", apiHandler.GetNombres).Methods("GET")
apiGroup.HandleFunc("/{id}", apiHandler.GetNombreByID).Methods("GET")
apiGroup.HandleFunc("/{id}", apiHandler.UpdateNombre).Methods("PUT")
apiGroup.HandleFunc("/{id}", apiHandler.DeleteNombre).Methods("DELETE")
apiGroup.HandleFunc("/html/edit/{id}", apiHandler.EditNombreHTML).Methods("GET")
apiGroup.HandleFunc("/html/update/{id}", apiHandler.UpdateNombreHTML).Methods("POST")
// HTML Routes // HTML Routes
apiGroup.GET("/html", apiHandler.GetNombresHTML) router.HandleFunc("/", apiHandler.GetIndexPlantsHTML).Methods("GET")
apiGroup.GET("/html/:id", apiHandler.GetNombreByIDHTML) router.HandleFunc("/nombres/html", apiHandler.GetNombresHTML).Methods("GET")
router.HandleFunc("/nombres/html/{id}", apiHandler.GetNombreByIDHTML).Methods("GET")
// Start the server // Start the server
if err := router.Run(":8080"); err != nil { log.Println("Server started on :8080")
if err := http.ListenAndServe(":8080", router); err != nil {
log.Fatalf("Failed to start server: %v", err) log.Fatalf("Failed to start server: %v", err)
} }
} }

5
go.mod
View file

@ -2,6 +2,8 @@ module go_selva
go 1.21.1 go 1.21.1
require github.com/joho/godotenv v1.5.1
require ( require (
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic v1.11.6 // indirect
@ -16,7 +18,7 @@ require (
github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/go-sql-driver/mysql v1.9.1 // indirect github.com/go-sql-driver/mysql v1.9.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/joho/godotenv v1.5.1 // indirect github.com/gorilla/mux v1.8.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
@ -33,5 +35,4 @@ require (
golang.org/x/text v0.15.0 // indirect golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
github.com/joho/godotenv v1.5.1
) )

2
go.sum
View file

@ -27,6 +27,8 @@ github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI6
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=

View file

@ -2,14 +2,16 @@ package api
import ( import (
"database/sql" "database/sql"
"encoding/json"
"html/template"
"log" "log"
"net/http" "net/http"
"strconv" "strconv"
"github.com/gin-gonic/gin" "github.com/gorilla/mux"
) )
// Nombre represents the structure of the Nombres table. // Nombre representa la estructura de la tabla Nombres (assuming this is your plant table).
type Nombre struct { type Nombre struct {
NombreID int `json:"NombreID"` NombreID int `json:"NombreID"`
FamiliaID int `json:"FamiliaID"` FamiliaID int `json:"FamiliaID"`
@ -18,44 +20,91 @@ type Nombre struct {
ProveedorID int `json:"ProveedorID"` ProveedorID int `json:"ProveedorID"`
Precio float64 `json:"Precio"` Precio float64 `json:"Precio"`
Inactivo bool `json:"Inactivo"` Inactivo bool `json:"Inactivo"`
// Add other relevant fields if you want to display them
} }
// APIHandler ... (rest of your struct definition)
// NewAPIHandler ... (rest of your function)
// GetIndexPlantsHTML recupera the first 20 plants from the Nombres table.
// internal/api/api.go
func (h *APIHandler) GetIndexPlantsHTML(w http.ResponseWriter, r *http.Request) {
log.Println("GetIndexPlantsHTML called")
rows, err := h.DB.Query("SELECT NombreID, FamiliaID, Nombre, Fecha, ProveedorID, Precio, Inactivo FROM Nombres WHERE Inactivo = 0 LIMIT 20")
if err != nil {
log.Println("Database query error:", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close()
var plants []Nombre
for rows.Next() {
var plant Nombre
if err := rows.Scan(&plant.NombreID, &plant.FamiliaID, &plant.Nombre, &plant.Fecha, &plant.ProveedorID, &plant.Precio, &plant.Inactivo); err != nil {
log.Println("Row scan error:", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
plants = append(plants, plant)
}
log.Println("Retrieved plants:", plants)
if err := h.template.ExecuteTemplate(w, "index.html", plants); err != nil {
log.Println("Template execution error:", err)
http.Error(w, "Failed to render HTML", http.StatusInternalServerError)
return
}
log.Println("index.html rendered")
}
// APIHandler maneja las operaciones de la API para la tabla Nombres.
type APIHandler struct { type APIHandler struct {
DB *sql.DB DB *sql.DB
template *template.Template // To store loaded HTML templates
} }
func NewAPIHandler(db *sql.DB) *APIHandler { // NewAPIHandler crea una nueva instancia de APIHandler.
return &APIHandler{DB: db} func NewAPIHandler(db *sql.DB, tmpl *template.Template) *APIHandler {
return &APIHandler{DB: db, template: tmpl}
} }
func (h *APIHandler) CreateNombre(c *gin.Context) { // CreateNombre crea un nuevo registro en la tabla Nombres.
func (h *APIHandler) CreateNombre(w http.ResponseWriter, r *http.Request) {
var nombre Nombre var nombre Nombre
if err := c.ShouldBindJSON(&nombre); err != nil { if err := json.NewDecoder(r.Body).Decode(&nombre); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
result, err := h.DB.Exec("INSERT INTO Nombres (FamiliaID, Nombre, Fecha, ProveedorID, Precio, Inactivo) VALUES (?, ?, ?, ?, ?, ?)", result, err := h.DB.Exec("INSERT INTO Nombres (FamiliaID, Nombre, Fecha, ProveedorID, Precio, Inactivo) VALUES (?, ?, ?, ?, ?, ?)",
nombre.FamiliaID, nombre.Nombre, nombre.Fecha, nombre.ProveedorID, nombre.Precio, nombre.Inactivo) nombre.FamiliaID, nombre.Nombre, nombre.Fecha, nombre.ProveedorID, nombre.Precio, nombre.Inactivo)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
id, err := result.LastInsertId() id, err := result.LastInsertId()
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
nombre.NombreID = int(id) nombre.NombreID = int(id)
c.JSON(http.StatusCreated, nombre) w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(nombre)
} }
func (h *APIHandler) GetNombres(c *gin.Context) { // GetNombres recupera todos los registros de la tabla Nombres.
func (h *APIHandler) GetNombres(w http.ResponseWriter, r *http.Request) {
rows, err := h.DB.Query("SELECT NombreID, FamiliaID, Nombre, Fecha, ProveedorID, Precio, Inactivo FROM Nombres") rows, err := h.DB.Query("SELECT NombreID, FamiliaID, Nombre, Fecha, ProveedorID, Precio, Inactivo FROM Nombres")
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
defer rows.Close() defer rows.Close()
@ -64,19 +113,27 @@ func (h *APIHandler) GetNombres(c *gin.Context) {
for rows.Next() { for rows.Next() {
var nombre Nombre var nombre Nombre
if err := rows.Scan(&nombre.NombreID, &nombre.FamiliaID, &nombre.Nombre, &nombre.Fecha, &nombre.ProveedorID, &nombre.Precio, &nombre.Inactivo); err != nil { if err := rows.Scan(&nombre.NombreID, &nombre.FamiliaID, &nombre.Nombre, &nombre.Fecha, &nombre.ProveedorID, &nombre.Precio, &nombre.Inactivo); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
nombres = append(nombres, nombre) nombres = append(nombres, nombre)
} }
c.JSON(http.StatusOK, nombres) w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(nombres)
} }
func (h *APIHandler) GetNombreByID(c *gin.Context) { // GetNombreByID recupera un registro de la tabla Nombres por su ID.
id, err := strconv.Atoi(c.Param("id")) func (h *APIHandler) GetNombreByID(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
idStr, ok := vars["id"]
if !ok {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
id, err := strconv.Atoi(idStr)
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) http.Error(w, "Invalid ID", http.StatusBadRequest)
return return
} }
@ -85,50 +142,60 @@ func (h *APIHandler) GetNombreByID(c *gin.Context) {
&nombre.NombreID, &nombre.FamiliaID, &nombre.Nombre, &nombre.Fecha, &nombre.ProveedorID, &nombre.Precio, &nombre.Inactivo) &nombre.NombreID, &nombre.FamiliaID, &nombre.Nombre, &nombre.Fecha, &nombre.ProveedorID, &nombre.Precio, &nombre.Inactivo)
if err != nil { if err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
c.JSON(http.StatusNotFound, gin.H{"error": "Nombre not found"}) http.Error(w, "Nombre not found", http.StatusNotFound)
} else { } else {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) http.Error(w, err.Error(), http.StatusInternalServerError)
} }
return return
} }
c.JSON(http.StatusOK, nombre) w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(nombre)
} }
func (h *APIHandler) UpdateNombre(c *gin.Context) { // UpdateNombre actualiza un registro existente en la tabla Nombres.
id, err := strconv.Atoi(c.Param("id")) func (h *APIHandler) UpdateNombre(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
idStr, ok := vars["id"]
if !ok {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
id, err := strconv.Atoi(idStr)
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) http.Error(w, "Invalid ID", http.StatusBadRequest)
return return
} }
var nombre Nombre var nombre Nombre
if err := c.ShouldBindJSON(&nombre); err != nil { if err := json.NewDecoder(r.Body).Decode(&nombre); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
_, err = h.DB.Exec("UPDATE Nombres SET FamiliaID = ?, Nombre = ?, Fecha = ?, ProveedorID = ?, Precio = ?, Inactivo = ? WHERE NombreID = ?", _, err = h.DB.Exec("UPDATE Nombres SET FamiliaID = ?, Nombre = ?, Fecha = ?, ProveedorID = ?, Precio = ?, Inactivo = ? WHERE NombreID = ?",
nombre.FamiliaID, nombre.Nombre, nombre.Fecha, nombre.ProveedorID, nombre.Precio, nombre.Inactivo, id) nombre.FamiliaID, nombre.Nombre, nombre.Fecha, nombre.ProveedorID, nombre.Precio, nombre.Inactivo, id)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
nombre.NombreID = id nombre.NombreID = id
c.JSON(http.StatusOK, nombre) w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(nombre)
} }
func (h *APIHandler) SearchNombres(c *gin.Context) { // SearchNombres busca registros en la tabla Nombres por nombre.
nombre := c.Query("nombre") func (h *APIHandler) SearchNombres(w http.ResponseWriter, r *http.Request) {
nombre := r.URL.Query().Get("nombre")
if nombre == "" { if nombre == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Nombre parameter is required"}) http.Error(w, "Nombre parameter is required", http.StatusBadRequest)
return return
} }
rows, err := h.DB.Query("SELECT NombreID, FamiliaID, Nombre, Fecha, ProveedorID, Precio, Inactivo FROM Nombres WHERE Nombre = ?", nombre) rows, err := h.DB.Query("SELECT NombreID, FamiliaID, Nombre, Fecha, ProveedorID, Precio, Inactivo FROM Nombres WHERE Nombre = ?", nombre)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
defer rows.Close() defer rows.Close()
@ -137,37 +204,47 @@ func (h *APIHandler) SearchNombres(c *gin.Context) {
for rows.Next() { for rows.Next() {
var nombre Nombre var nombre Nombre
if err := rows.Scan(&nombre.NombreID, &nombre.FamiliaID, &nombre.Nombre, &nombre.Fecha, &nombre.ProveedorID, &nombre.Precio, &nombre.Inactivo); err != nil { if err := rows.Scan(&nombre.NombreID, &nombre.FamiliaID, &nombre.Nombre, &nombre.Fecha, &nombre.ProveedorID, &nombre.Precio, &nombre.Inactivo); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
nombres = append(nombres, nombre) nombres = append(nombres, nombre)
} }
c.JSON(http.StatusOK, nombres) w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(nombres)
} }
func (h *APIHandler) DeleteNombre(c *gin.Context) { // DeleteNombre elimina un registro de la tabla Nombres por su ID.
id, err := strconv.Atoi(c.Param("id")) func (h *APIHandler) DeleteNombre(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
idStr, ok := vars["id"]
if !ok {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
id, err := strconv.Atoi(idStr)
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) http.Error(w, "Invalid ID", http.StatusBadRequest)
return return
} }
_, err = h.DB.Exec("DELETE FROM Nombres WHERE NombreID = ?", id) _, err = h.DB.Exec("DELETE FROM Nombres WHERE NombreID = ?", id)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
c.JSON(http.StatusOK, gin.H{"message": "Nombre deleted"}) w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"message": "Nombre deleted"})
} }
func (h *APIHandler) GetNombresHTML(c *gin.Context) { // GetNombresHTML recupera todos los registros de la tabla Nombres y los muestra en una página HTML.
log.Println("GetNombresHTML called") // Add logging func (h *APIHandler) GetNombresHTML(w http.ResponseWriter, r *http.Request) {
log.Println("GetNombresHTML called")
rows, err := h.DB.Query("SELECT NombreID, FamiliaID, Nombre, Fecha, ProveedorID, Precio, Inactivo FROM Nombres") rows, err := h.DB.Query("SELECT NombreID, FamiliaID, Nombre, Fecha, ProveedorID, Precio, Inactivo FROM Nombres")
if err != nil { if err != nil {
log.Println("Database query error:", err) // Add logging log.Println("Database query error:", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
defer rows.Close() defer rows.Close()
@ -176,94 +253,185 @@ func (h *APIHandler) GetNombresHTML(c *gin.Context) {
for rows.Next() { for rows.Next() {
var nombre Nombre var nombre Nombre
if err := rows.Scan(&nombre.NombreID, &nombre.FamiliaID, &nombre.Nombre, &nombre.Fecha, &nombre.ProveedorID, &nombre.Precio, &nombre.Inactivo); err != nil { if err := rows.Scan(&nombre.NombreID, &nombre.FamiliaID, &nombre.Nombre, &nombre.Fecha, &nombre.ProveedorID, &nombre.Precio, &nombre.Inactivo); err != nil {
log.Println("Row scan error:", err) // Add logging log.Println("Row scan error:", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
nombres = append(nombres, nombre) nombres = append(nombres, nombre)
} }
log.Println("Retrieved nombres:", nombres) // Add logging log.Println("Retrieved nombres:", nombres)
c.HTML(http.StatusOK, "nombres.html", nombres) if err := h.template.ExecuteTemplate(w, "nombres.html", nombres); err != nil {
log.Println("nombres.html rendered") //add log log.Println("Template execution error:", err)
http.Error(w, "Failed to render HTML", http.StatusInternalServerError)
return
}
log.Println("nombres.html rendered")
} }
func (h *APIHandler) GetNombreByIDHTML(c *gin.Context) { // GetNombreByIDHTML recupera un registro de la tabla Nombres por su ID, el nombre de su familia,
id, err := strconv.Atoi(c.Param("id")) // y el nombre del proveedor, luego los muestra en una página HTML.
func (h *APIHandler) GetNombreByIDHTML(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
idStr, ok := vars["id"]
if !ok {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
id, err := strconv.Atoi(idStr)
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
var nombreDetalle struct {
Nombre Nombre
FamiliaName string
ProveedorName string
}
// Join nombres, familias, and proveedores tables
err = h.DB.QueryRow(`
SELECT
n.NombreID,
n.FamiliaID,
n.Nombre,
n.Fecha,
n.ProveedorID,
n.Precio,
n.Inactivo,
f.Familia,
p.Nombre
FROM nombres n
INNER JOIN familias f ON n.FamiliaID = f.FamiliaID
INNER JOIN proveedores p ON n.ProveedorID = p.ProveedorID
WHERE n.NombreID = ?
`, id).Scan(
&nombreDetalle.Nombre.NombreID,
&nombreDetalle.Nombre.FamiliaID,
&nombreDetalle.Nombre.Nombre,
&nombreDetalle.Nombre.Fecha,
&nombreDetalle.Nombre.ProveedorID,
&nombreDetalle.Nombre.Precio,
&nombreDetalle.Nombre.Inactivo,
&nombreDetalle.FamiliaName,
&nombreDetalle.ProveedorName,
)
if err != nil {
if err == sql.ErrNoRows {
http.Error(w, "Nombre not found", http.StatusNotFound)
} else {
log.Println("Database query error:", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
if err := h.template.ExecuteTemplate(w, "nombre.html", nombreDetalle); err != nil {
log.Println("Template execution error:", err)
http.Error(w, "Failed to render HTML", http.StatusInternalServerError)
return
}
}
func (h *APIHandler) EditNombreHTML(w http.ResponseWriter, r *http.Request) {
log.Println("EditNombreHTML called")
vars := mux.Vars(r)
idStr, ok := vars["id"]
log.Printf("Extracted ID string: %s, ok: %v", idStr, ok)
if !ok {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
id, err := strconv.Atoi(idStr)
log.Printf("Converted ID: %d, error: %v", id, err)
if err != nil {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return return
} }
var nombre Nombre var nombre Nombre
err = h.DB.QueryRow("SELECT NombreID, FamiliaID, Nombre, Fecha, ProveedorID, Precio, Inactivo FROM Nombres WHERE NombreID = ?", id).Scan( err = h.DB.QueryRow("SELECT NombreID, FamiliaID, Nombre, Fecha, ProveedorID, Precio, Inactivo FROM Nombres WHERE NombreID = ?", id).Scan(
&nombre.NombreID, &nombre.FamiliaID, &nombre.Nombre, &nombre.Fecha, &nombre.ProveedorID, &nombre.Precio, &nombre.Inactivo) &nombre.NombreID, &nombre.FamiliaID, &nombre.Nombre, &nombre.Fecha, &nombre.ProveedorID, &nombre.Precio, &nombre.Inactivo)
log.Printf("Database query result: %+v, error: %v", nombre, err)
if err != nil { if err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
c.JSON(http.StatusNotFound, gin.H{"error": "Nombre not found"}) http.Error(w, "Nombre not found", http.StatusNotFound)
} else { } else {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) log.Println("Database query error:", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
} }
return return
} }
c.HTML(http.StatusOK, "nombre.html", nombre) log.Printf("Data passed to template: %+v", nombre)
}
func (h *APIHandler) EditNombreHTML(c *gin.Context) { w.Header().Set("Content-Type", "text/html; charset=utf-8") // Set header BEFORE executing template
id, err := strconv.Atoi(c.Param("id"))
if err != nil { if err := h.template.ExecuteTemplate(w, "edit_nombre.html", nombre); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) log.Println("Template execution error:", err)
http.Error(w, "Failed to render HTML", http.StatusInternalServerError)
return return
} }
log.Println("edit_nombre.html rendered")
var nombre Nombre
err = h.DB.QueryRow("SELECT NombreID, FamiliaID, Nombre, Fecha, ProveedorID, Precio, Inactivo FROM Nombres WHERE NombreID = ?", id).Scan(
&nombre.NombreID, &nombre.FamiliaID, &nombre.Nombre, &nombre.Fecha, &nombre.ProveedorID, &nombre.Precio, &nombre.Inactivo)
if err != nil {
if err == sql.ErrNoRows {
c.JSON(http.StatusNotFound, gin.H{"error": "Nombre not found"})
} else {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
return
}
c.HTML(http.StatusOK, "edit_nombre.html", nombre)
} }
func (h *APIHandler) UpdateNombreHTML(c *gin.Context) { // UpdateNombreHTML actualiza un registro en la tabla Nombres desde una página HTML.
id, err := strconv.Atoi(c.Param("id")) // Declare id and err func (h *APIHandler) UpdateNombreHTML(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
idStr, ok := vars["id"]
if !ok {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
id, err := strconv.Atoi(idStr)
if err != nil { if err != nil {
log.Println("Invalid ID:", err) log.Println("Invalid ID:", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) http.Error(w, "Invalid ID", http.StatusBadRequest)
return return
} }
var nombre Nombre if err := r.ParseForm(); err != nil {
if err := c.ShouldBind(&nombre); err != nil { // Declare err log.Println("Form parsing error:", err)
log.Println("Binding error:", err) http.Error(w, "Failed to parse form", http.StatusBadRequest)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return return
} }
nombre := Nombre{
NombreID: id,
FamiliaID: atoi(r.FormValue("FamiliaID")),
Nombre: r.FormValue("Nombre"),
Fecha: r.FormValue("Fecha"),
ProveedorID: atoi(r.FormValue("ProveedorID")),
Precio: atof64(r.FormValue("Precio")),
Inactivo: r.FormValue("Inactivo") == "on",
}
log.Println("Updating nombre:", nombre) log.Println("Updating nombre:", nombre)
// Handle empty date
fecha := nombre.Fecha fecha := nombre.Fecha
if fecha == "" { if fecha == "" {
fecha = "0000-00-00" // Or "NULL", if your database allows it fecha = "0000-00-00"
} }
_, err = h.DB.Exec("UPDATE Nombres SET FamiliaID = ?, Nombre = ?, Fecha = ?, ProveedorID = ?, Precio = ?, Inactivo = ? WHERE NombreID = ?", _, err = h.DB.Exec("UPDATE Nombres SET FamiliaID = ?, Nombre = ?, Fecha = ?, ProveedorID = ?, Precio = ?, Inactivo = ? WHERE NombreID = ?",
nombre.FamiliaID, nombre.Nombre, fecha, nombre.ProveedorID, nombre.Precio, nombre.Inactivo, id) // Use declared id and err nombre.FamiliaID, nombre.Nombre, fecha, nombre.ProveedorID, nombre.Precio, nombre.Inactivo, id)
if err != nil { if err != nil {
log.Println("Database update error:", err) log.Println("Database update error:", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
log.Println("Nombre updated successfully") log.Println("Nombre updated successfully")
http.Redirect(w, r, "/nombres/html/"+strconv.Itoa(id), http.StatusFound)
c.Redirect(http.StatusFound, "/nombres/html/"+strconv.Itoa(id)) }
func atoi(s string) int {
i, _ := strconv.Atoi(s) // Ignore error for simplicity in this context
return i
}
func atof64(s string) float64 {
f, _ := strconv.ParseFloat(s, 64) // Ignore error for simplicity
return f
} }

173
misc/go_selva.sql Normal file
View file

@ -0,0 +1,173 @@
-- --------------------------------------------------------
-- Host: 127.0.0.1
-- Server version: 11.7.2-MariaDB - mariadb.org binary distribution
-- Server OS: Win64
-- HeidiSQL Version: 12.10.0.7000
-- --------------------------------------------------------
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
-- Dumping database structure for go_selva
CREATE DATABASE IF NOT EXISTS `go_selva` /*!40100 DEFAULT CHARACTER SET utf32 COLLATE utf32_unicode_ci */;
USE `go_selva`;
-- Dumping structure for table go_selva.familias
CREATE TABLE IF NOT EXISTS `familias` (
`FamiliaID` int(11) NOT NULL AUTO_INCREMENT,
`Familia` varchar(35) NOT NULL,
PRIMARY KEY (`FamiliaID`),
UNIQUE KEY `Familia` (`Familia`)
) ENGINE=InnoDB AUTO_INCREMENT=112 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
-- Data exporting was unselected.
-- Dumping structure for table go_selva.fotos
CREATE TABLE IF NOT EXISTS `fotos` (
`FotoID` int(11) NOT NULL AUTO_INCREMENT,
`NombreID` int(11) NOT NULL DEFAULT 0,
`Direccion` varchar(75) NOT NULL,
`HayVideo` char(1) DEFAULT '0',
`Video` varchar(75) DEFAULT '',
PRIMARY KEY (`FotoID`),
KEY `NombreID` (`NombreID`),
CONSTRAINT `Fotos_ibfk_1` FOREIGN KEY (`NombreID`) REFERENCES `nombres` (`NombreID`)
) ENGINE=InnoDB AUTO_INCREMENT=1093 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
-- Data exporting was unselected.
-- Dumping structure for table go_selva.logs
CREATE TABLE IF NOT EXISTS `logs` (
`LogID` int(9) NOT NULL AUTO_INCREMENT,
`Tiempo` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`UsuarioID` int(4) NOT NULL DEFAULT 0,
`Query` text NOT NULL,
PRIMARY KEY (`LogID`)
) ENGINE=InnoDB AUTO_INCREMENT=14427 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
-- Data exporting was unselected.
-- Dumping structure for table go_selva.logs20191029
CREATE TABLE IF NOT EXISTS `logs20191029` (
`LogID` int(9) NOT NULL DEFAULT 0,
`Tiempo` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`UsuarioID` int(4) NOT NULL DEFAULT 0,
`Query` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
-- Data exporting was unselected.
-- Dumping structure for table go_selva.nombres
CREATE TABLE IF NOT EXISTS `nombres` (
`NombreID` int(11) NOT NULL AUTO_INCREMENT,
`FamiliaID` int(6) NOT NULL DEFAULT 1,
`Nombre` varchar(75) NOT NULL,
`Fecha` date NOT NULL DEFAULT '0000-00-00',
`ProveedorID` int(4) NOT NULL DEFAULT 1,
`Precio` float(10,2) NOT NULL DEFAULT 0.00,
`Inactivo` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`NombreID`),
UNIQUE KEY `Nombre` (`Nombre`),
KEY `FamiliaID` (`FamiliaID`),
KEY `ProveedorID` (`ProveedorID`),
CONSTRAINT `Nombres_ibfk_1` FOREIGN KEY (`FamiliaID`) REFERENCES `familias` (`FamiliaID`),
CONSTRAINT `Nombres_ibfk_2` FOREIGN KEY (`ProveedorID`) REFERENCES `proveedores` (`ProveedorID`)
) ENGINE=InnoDB AUTO_INCREMENT=1140 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
-- Data exporting was unselected.
-- Dumping structure for table go_selva.nombresvulgares
CREATE TABLE IF NOT EXISTS `nombresvulgares` (
`NombreVulgarID` int(11) NOT NULL AUTO_INCREMENT,
`NombreID` int(11) NOT NULL DEFAULT 0,
`NombreVulgar` varchar(50) DEFAULT NULL,
PRIMARY KEY (`NombreVulgarID`),
KEY `NombreID` (`NombreID`),
CONSTRAINT `NombresVulgares_ibfk_1` FOREIGN KEY (`NombreID`) REFERENCES `nombres` (`NombreID`)
) ENGINE=InnoDB AUTO_INCREMENT=2453 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
-- Data exporting was unselected.
-- Dumping structure for table go_selva.notas
CREATE TABLE IF NOT EXISTS `notas` (
`NotaID` int(11) NOT NULL AUTO_INCREMENT,
`NombreID` int(11) NOT NULL,
`Nota` tinytext DEFAULT NULL,
PRIMARY KEY (`NotaID`),
KEY `NombreID` (`NombreID`),
CONSTRAINT `Notas_ibfk_1` FOREIGN KEY (`NombreID`) REFERENCES `nombres` (`NombreID`)
) ENGINE=InnoDB AUTO_INCREMENT=6426 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
-- Data exporting was unselected.
-- Dumping structure for table go_selva.proveedores
CREATE TABLE IF NOT EXISTS `proveedores` (
`ProveedorID` int(3) NOT NULL AUTO_INCREMENT,
`Nombre` varchar(95) NOT NULL DEFAULT '',
`RFC` varchar(15) DEFAULT '',
`Direccion` varchar(75) DEFAULT '',
`Colonia` varchar(50) DEFAULT '',
`Ciudad` varchar(25) DEFAULT '',
`Estado` varchar(40) DEFAULT '',
`Pais` varchar(25) DEFAULT '',
`CP` varchar(20) DEFAULT '',
`Tel1` varchar(20) DEFAULT '',
`Tel2` varchar(20) DEFAULT '',
`URL` varchar(75) DEFAULT NULL,
`Desactivado` char(1) NOT NULL DEFAULT 'N',
`Observaciones` varchar(150) DEFAULT NULL,
`Contacto1Nombre` varchar(75) DEFAULT '',
`Contacto1Tel` varchar(20) DEFAULT '',
`Contacto1Cel` varchar(20) DEFAULT '',
`Contacto1EMail` varchar(75) DEFAULT '',
`Contacto2Nombre` varchar(75) DEFAULT '',
`Contacto2Tel` varchar(20) DEFAULT '',
`Contacto2Cel` varchar(20) DEFAULT '',
`Contacto2EMail` varchar(75) DEFAULT '',
PRIMARY KEY (`ProveedorID`),
UNIQUE KEY `Nombre` (`Nombre`)
) ENGINE=InnoDB AUTO_INCREMENT=58 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
-- Data exporting was unselected.
-- Dumping structure for table go_selva.ubicaciones
CREATE TABLE IF NOT EXISTS `ubicaciones` (
`UbiID` int(11) NOT NULL AUTO_INCREMENT,
`NombreID` int(11) NOT NULL,
`Ubicacion` varchar(20) NOT NULL,
PRIMARY KEY (`UbiID`),
KEY `NombreID` (`NombreID`),
CONSTRAINT `Ubicaciones_ibfk_1` FOREIGN KEY (`NombreID`) REFERENCES `nombres` (`NombreID`)
) ENGINE=InnoDB AUTO_INCREMENT=1665 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
-- Data exporting was unselected.
-- Dumping structure for table go_selva.usuarios
CREATE TABLE IF NOT EXISTS `usuarios` (
`UID` int(5) NOT NULL AUTO_INCREMENT,
`ApellidoPaterno` varchar(25) NOT NULL DEFAULT '',
`ApellidoMaterno` varchar(25) DEFAULT '',
`Nombres` varchar(30) NOT NULL DEFAULT '',
`Login` varchar(25) NOT NULL DEFAULT '',
`PWD` varchar(70) NOT NULL DEFAULT '',
`Fecha` date NOT NULL DEFAULT '0000-00-00',
`Nivel` enum('Admin','Capturista','Almacenista','Consulta','Instalador','Vendedor','PedOrdInv') DEFAULT NULL,
`Deshabilitado` char(1) NOT NULL DEFAULT 'N',
PRIMARY KEY (`UID`),
UNIQUE KEY `Login` (`Login`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
-- Data exporting was unselected.
/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */;
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */;

View file

@ -1,22 +1,105 @@
body { body {
font-family: sans-serif; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 20px;
background-color: #f4f4f4;
color: #333;
line-height: 1.6;
}
h1, h2 {
color: #007bff;
margin-bottom: 20px;
} }
table { table {
border-collapse: collapse; border-collapse: collapse;
width: 100%; width: 100%;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: white;
border-radius: 8px;
overflow: hidden; /* Para que los bordes redondeados funcionen bien */
} }
th, td { th, td {
border: 1px solid #ddd; border: 1px solid #e0e0e0;
padding: 8px; padding: 12px 15px;
text-align: left; text-align: left;
} }
th { th {
background-color: #f2f2f2; background-color: #f0f8ff; /* Un azul muy claro para los encabezados */
font-weight: 600;
} }
tr:nth-child(even) { tr:nth-child(even) {
background-color: #f9f9f9; background-color: #f9f9f9;
} }
tr:hover {
background-color: #f0f0f0; /* Efecto hover para las filas */
}
a.button, button.button {
display: inline-block;
padding: 10px 15px;
background-color: #007bff;
color: white;
text-decoration: none;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
margin: 5px;
}
a.button:hover, button.button:hover {
background-color: #0056b3;
}
.button.delete {
background-color: #dc3545;
}
.button.delete:hover {
background-color: #c82333;
}
form {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 600;
}
input[type="text"],
input[type="number"],
input[type="date"],
select {
width: calc(100% - 22px); /* Ajuste para el padding y el borde */
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 5px;
box-sizing: border-box;
}
input[type="checkbox"]{
margin-bottom: 15px;
}
@media (max-width: 600px) {
table, form {
width: 100%;
}
th, td {
padding: 8px;
font-size: 0.9em;
}
}

View file

@ -1,21 +1,41 @@
<form action="/nombres/html/update/{{ .NombreID }}" method="POST"> <!DOCTYPE html>
<label for="FamiliaID">FamiliaID:</label> <html lang="en">
<input type="number" id="FamiliaID" name="FamiliaID" value="{{ .FamiliaID }}"><br><br>
<label for="Nombre">Nombre:</label> <head>
<input type="text" id="Nombre" name="Nombre" value="{{ .Nombre }}"><br><br> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Editar Nombre</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<label for="Fecha">Fecha:</label> <body>
<input type="date" id="Fecha" name="Fecha" value="{{ .Fecha }}"><br><br> <h1>Editar Nombre</h1>
<label for="ProveedorID">ProveedorID:</label> <form action="/nombres/html/update/{{ .NombreID }}" method="POST">
<input type="number" id="ProveedorID" name="ProveedorID" value="{{ .ProveedorID }}"><br><br> <label for="FamiliaID">FamiliaID:</label>
<input type="number" id="FamiliaID" name="FamiliaID" value="{{ .FamiliaID }}"><br><br>
<label for="Precio">Precio:</label> <label for="Nombre">Nombre:</label>
<input type="number" id="Precio" name="Precio" value="{{ .Precio }}"><br><br> <input type="text" id="Nombre" name="Nombre" value="{{ .Nombre }}"><br><br>
<label for="Inactivo">Inactivo:</label> <label for="Fecha">Fecha:</label>
<input type="checkbox" id="Inactivo" name="Inactivo" {{ if .Inactivo }}checked{{ end }}><br><br> <input type="date" id="Fecha" name="Fecha" value="{{ .Fecha }}"><br><br>
<input type="submit" value="Update"> <label for="ProveedorID">ProveedorID:</label>
</form> <input type="number" id="ProveedorID" name="ProveedorID" value="{{ .ProveedorID }}"><br><br>
<label for="Precio">Precio:</label>
<input type="number" step="0.01" id="Precio" name="Precio" value="{{ .Precio }}"><br><br>
<label for="Inactivo">Inactivo:</label>
<input type="checkbox" id="Inactivo" name="Inactivo" {{ if .Inactivo }}checked{{ end }}><br><br>
<button type="submit">Guardar Cambios</button>
</form>
<form action="/nombres/html/{{ .NombreID }}" method="GET">
<button type="submit">Volver al Detalle</button>
</form>
</body>
</html>

22
templates/index.html Normal file
View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Plant Index</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<h1>Our Plants</h1>
<ul>
{{ range . }}
<li>
<a href="/nombres/html/{{ .NombreID }}">{{ .Nombre }}</a>
</li>
{{ end }}
</ul>
</body>
</html>

View file

@ -1,53 +1,49 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<title>Nombre Details</title> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Detalle del Nombre</title>
<link rel="stylesheet" href="/static/style.css"> <link rel="stylesheet" href="/static/style.css">
</head> </head>
<body> <body>
<h1>Nombre Details</h1> <h1>Detalle del Nombre</h1>
{{ if . }}
<table> <table>
<thead>
<tr>
<th>Field</th>
<th>Value</th>
</tr>
</thead>
<tbody> <tbody>
<tr> <tr>
<td>NombreID</td> <th>Nombre:</th>
<td>{{ .NombreID }}</td> <td>{{ .Nombre.Nombre }}</td>
</tr> </tr>
<tr> <tr>
<td>FamiliaID</td> <th>Familia:</th>
<td>{{ .FamiliaID }}</td> <td>{{ .FamiliaName }}</td>
</tr> </tr>
<tr> <tr>
<td>Nombre</td> <th>Fecha:</th>
<td>{{ .Nombre | safeHTML }}</td> <td>{{ .Nombre.Fecha }}</td>
</tr> </tr>
<tr> <tr>
<td>Fecha</td> <th>Proveedor:</th>
<td>{{ .Fecha }}</td> <td>{{ .ProveedorName }}</td>
</tr> </tr>
<tr> <tr>
<td>ProveedorID</td> <th>Precio:</th>
<td>{{ .ProveedorID }}</td> <td>{{ .Nombre.Precio }}</td>
</tr> </tr>
<tr> <tr>
<td>Precio</td> <th>Inactivo:</th>
<td>{{ .Precio }}</td> <td>{{ .Nombre.Inactivo }}</td>
</tr>
<tr>
<td>Inactivo</td>
<td>{{ .Inactivo }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<a href="/nombres/html">Back to List</a> <p><a href="/nombres/html/edit/{{ .Nombre.NombreID }}">Editar</a> | <a href="/nombres/html">Volver</a></p>
<a href="/nombres/html/edit/{{ .NombreID }}">Edit</a> {{ else }}
<p>Nombre no encontrado.</p>
{{ end }}
</body> </body>
</html> </html>