This commit is contained in:
Lizandro Guarnizo
2026-05-16 15:06:05 -05:00
parent fef471ff6c
commit 9820405475
3 changed files with 50 additions and 38 deletions
+11 -9
View File
@@ -20,9 +20,10 @@ type PortalUser struct {
ClienteID *uint `json:"cliente_id" gorm:"column:cliente_id;index"` // nil si es partner ClienteID *uint `json:"cliente_id" gorm:"column:cliente_id;index"` // nil si es partner
Cliente *Cliente `json:"cliente" gorm:"foreignKey:ClienteID"` Cliente *Cliente `json:"cliente" gorm:"foreignKey:ClienteID"`
Rol string `json:"rol" gorm:"column:rol;default:'cliente'"` // cliente|partner Rol string `json:"rol" gorm:"column:rol;default:'cliente'"` // cliente|partner
Activo bool `json:"activo" gorm:"column:activo;default:true"` Activo bool `json:"activo" gorm:"column:activo;default:true"`
Notas string `json:"notas" gorm:"column:notas;type:text"` Notas string `json:"notas" gorm:"column:notas;type:text"`
PortalAccesos []PortalAcceso `json:"portal_accesos" gorm:"foreignKey:PortalUserID"` TelegramChatID string `json:"telegram_chat_id" gorm:"column:telegram_chat_id"`
PortalAccesos []PortalAcceso `json:"portal_accesos" gorm:"foreignKey:PortalUserID"`
} }
func (PortalUser) TableName() string { return "portal_users" } func (PortalUser) TableName() string { return "portal_users" }
@@ -71,12 +72,13 @@ func CreatePortalUser(u *PortalUser) error {
func UpdatePortalUser(u *PortalUser) error { func UpdatePortalUser(u *PortalUser) error {
return app.Http.Database.DB.Model(&PortalUser{}).Where("id = ?", u.ID).Updates(map[string]interface{}{ return app.Http.Database.DB.Model(&PortalUser{}).Where("id = ?", u.ID).Updates(map[string]interface{}{
"nombre": u.Nombre, "nombre": u.Nombre,
"email": u.Email, "email": u.Email,
"cliente_id": u.ClienteID, "cliente_id": u.ClienteID,
"rol": u.Rol, "rol": u.Rol,
"activo": u.Activo, "activo": u.Activo,
"notas": u.Notas, "notas": u.Notas,
"telegram_chat_id": u.TelegramChatID,
}).Error }).Error
} }
+9 -3
View File
@@ -21,6 +21,7 @@
<th class="px-4 py-3 text-left">Rol</th> <th class="px-4 py-3 text-left">Rol</th>
<th class="px-4 py-3 text-left">Cliente / Accesos</th> <th class="px-4 py-3 text-left">Cliente / Accesos</th>
<th class="px-4 py-3 text-left">Estado</th> <th class="px-4 py-3 text-left">Estado</th>
<th class="px-4 py-3 text-left">Telegram</th>
<th class="px-4 py-3 text-left">Acciones</th> <th class="px-4 py-3 text-left">Acciones</th>
</tr> </tr>
</thead> </thead>
@@ -54,6 +55,10 @@
<td class="px-4 py-3"> <td class="px-4 py-3">
<span :class="u.activo ? 'badge-green' : 'badge-slate'" class="badge" x-text="u.activo ? 'Activo' : 'Inactivo'"></span> <span :class="u.activo ? 'badge-green' : 'badge-slate'" class="badge" x-text="u.activo ? 'Activo' : 'Inactivo'"></span>
</td> </td>
<td class="px-4 py-3 text-slate-500 text-xs">
<span x-show="u.telegram_chat_id" x-text="u.telegram_chat_id" class="font-mono"></span>
<span x-show="!u.telegram_chat_id" class="text-slate-300"></span>
</td>
<td class="px-4 py-3 flex gap-2"> <td class="px-4 py-3 flex gap-2">
<button @click="openEdit(u)" class="btn-icon text-yellow-500" title="Editar"> <button @click="openEdit(u)" class="btn-icon text-yellow-500" title="Editar">
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg> <svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
@@ -101,6 +106,7 @@
<input type="checkbox" x-model="form.activo" class="w-4 h-4"> <input type="checkbox" x-model="form.activo" class="w-4 h-4">
</div> </div>
<div><label class="label">Notas</label><textarea x-model="form.notas" class="input-field w-full" rows="2"></textarea></div> <div><label class="label">Notas</label><textarea x-model="form.notas" class="input-field w-full" rows="2"></textarea></div>
<div><label class="label">Chat ID de Telegram</label><input x-model="form.telegram_chat_id" class="input-field w-full font-mono" placeholder="Ej: 123456789"><p class="text-xs text-slate-400 mt-1">Se usa para enviar notificaciones al usuario vía Telegram.</p></div>
</div> </div>
<p x-show="error" x-text="error" class="text-red-500 text-sm mt-3"></p> <p x-show="error" x-text="error" class="text-red-500 text-sm mt-3"></p>
<div class="flex justify-end gap-3 mt-5"> <div class="flex justify-end gap-3 mt-5">
@@ -150,7 +156,7 @@ function portalUsuariosApp() {
showModal: false, showDelete: false, showAccesoModal: false, showModal: false, showDelete: false, showAccesoModal: false,
editId: null, deleteId: null, error: '', editId: null, deleteId: null, error: '',
accesoUserId: null, accesoClienteId: '', accesoUserId: null, accesoClienteId: '',
form: { nombre:'', email:'', password:'', rol:'cliente', cliente_id:'', activo:true, notas:'' }, form: { nombre:'', email:'', password:'', rol:'cliente', cliente_id:'', activo:true, notas:'', telegram_chat_id:'' },
async init() { await this.load(); }, async init() { await this.load(); },
@@ -165,12 +171,12 @@ function portalUsuariosApp() {
openCreate() { openCreate() {
this.editId=null; this.error=''; this.editId=null; this.error='';
this.form={nombre:'',email:'',password:'',rol:'cliente',cliente_id:'',activo:true,notas:''}; this.form={nombre:'',email:'',password:'',rol:'cliente',cliente_id:'',activo:true,notas:'',telegram_chat_id:''};
this.showModal=true; this.showModal=true;
}, },
openEdit(u) { openEdit(u) {
this.editId=u.ID; this.error=''; this.editId=u.ID; this.error='';
this.form={nombre:u.nombre,email:u.email,password:'',rol:u.rol,cliente_id:u.cliente_id||'',activo:u.activo,notas:u.notas||''}; this.form={nombre:u.nombre,email:u.email,password:'',rol:u.rol,cliente_id:u.cliente_id||'',activo:u.activo,notas:u.notas||'',telegram_chat_id:u.telegram_chat_id||''};
this.showModal=true; this.showModal=true;
}, },
async save() { async save() {
+30 -26
View File
@@ -40,12 +40,13 @@ func LoadPortalUsuarios(c *fiber.Ctx) error {
func CreatePortalUsuario(c *fiber.Ctx) error { func CreatePortalUsuario(c *fiber.Ctx) error {
type Req struct { type Req struct {
Nombre string `json:"nombre"` Nombre string `json:"nombre"`
Email string `json:"email"` Email string `json:"email"`
Password string `json:"password"` Password string `json:"password"`
ClienteID *uint `json:"cliente_id"` ClienteID *uint `json:"cliente_id"`
Rol string `json:"rol"` Rol string `json:"rol"`
Notas string `json:"notas"` Notas string `json:"notas"`
TelegramChatID string `json:"telegram_chat_id"`
} }
var req Req var req Req
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@@ -62,13 +63,14 @@ func CreatePortalUsuario(c *fiber.Ctx) error {
req.Rol = "cliente" req.Rol = "cliente"
} }
u := &models.PortalUser{ u := &models.PortalUser{
Nombre: req.Nombre, Nombre: req.Nombre,
Email: strings.ToLower(strings.TrimSpace(req.Email)), Email: strings.ToLower(strings.TrimSpace(req.Email)),
Password: hashed, Password: hashed,
ClienteID: req.ClienteID, ClienteID: req.ClienteID,
Rol: req.Rol, Rol: req.Rol,
Activo: true, Activo: true,
Notas: req.Notas, Notas: req.Notas,
TelegramChatID: req.TelegramChatID,
} }
if err := models.CreatePortalUser(u); err != nil { if err := models.CreatePortalUser(u); err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()}) return c.Status(500).JSON(fiber.Map{"error": err.Error()})
@@ -83,25 +85,27 @@ func UpdatePortalUsuario(c *fiber.Ctx) error {
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"}) return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
} }
type Req struct { type Req struct {
Nombre string `json:"nombre"` Nombre string `json:"nombre"`
Email string `json:"email"` Email string `json:"email"`
Password string `json:"password"` Password string `json:"password"`
ClienteID *uint `json:"cliente_id"` ClienteID *uint `json:"cliente_id"`
Rol string `json:"rol"` Rol string `json:"rol"`
Activo bool `json:"activo"` Activo bool `json:"activo"`
Notas string `json:"notas"` Notas string `json:"notas"`
TelegramChatID string `json:"telegram_chat_id"`
} }
var req Req var req Req
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
return c.Status(400).JSON(fiber.Map{"error": err.Error()}) return c.Status(400).JSON(fiber.Map{"error": err.Error()})
} }
u := &models.PortalUser{ u := &models.PortalUser{
Nombre: req.Nombre, Nombre: req.Nombre,
Email: strings.ToLower(strings.TrimSpace(req.Email)), Email: strings.ToLower(strings.TrimSpace(req.Email)),
ClienteID: req.ClienteID, ClienteID: req.ClienteID,
Rol: req.Rol, Rol: req.Rol,
Activo: req.Activo, Activo: req.Activo,
Notas: req.Notas, Notas: req.Notas,
TelegramChatID: req.TelegramChatID,
} }
u.ID = uint(id) u.ID = uint(id)
if err := models.UpdatePortalUser(u); err != nil { if err := models.UpdatePortalUser(u); err != nil {