diff --git a/resources/views/vcard_api.html b/resources/views/vcard_api.html
index 1172dab..62cc243 100644
--- a/resources/views/vcard_api.html
+++ b/resources/views/vcard_api.html
@@ -333,11 +333,34 @@
class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-[#8eb02f]" />
Sin barra final. Ej: https://midominio.com
+
+
+
Obtener token automáticamente
+
+
+
+
-
Token Sanctum de un usuario con rol administrador.
+
Se rellena automáticamente al obtener el token, o pégalo manualmente.
@@ -441,6 +464,10 @@ function vcardApiApp() {
configModal: false,
configError: '',
form: { nombre: '', base_url: '', bearer_token: '', activo: true },
+ loginForm: { email: '', password: '' },
+ loginLoading: false,
+ loginMsg: '',
+ loginOk: false,
membresiaModal: false,
membresiaUser: null,
membresiaInfo: null,
@@ -472,6 +499,30 @@ function vcardApiApp() {
} catch(e) {}
},
+ async obtenerToken() {
+ this.loginMsg = '';
+ if (!this.form.base_url.trim()) { this.loginMsg = 'Ingresa primero la URL Base.'; this.loginOk = false; return; }
+ if (!this.loginForm.email || !this.loginForm.password) { this.loginMsg = 'Email y contraseña son requeridos.'; this.loginOk = false; return; }
+ this.loginLoading = true;
+ try {
+ const r = await fetch('/app/vcard-api/login', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ base_url: this.form.base_url, email: this.loginForm.email, password: this.loginForm.password })
+ });
+ const d = await r.json();
+ if (d.ok && d.token) {
+ this.form.bearer_token = d.token;
+ this.loginMsg = '✓ Token obtenido. Haz clic en Guardar para aplicar.';
+ this.loginOk = true;
+ } else {
+ this.loginMsg = d.error || 'Error al obtener el token.';
+ this.loginOk = false;
+ }
+ } catch(e) { this.loginMsg = e.message; this.loginOk = false; }
+ this.loginLoading = false;
+ },
+
async saveConfig() {
this.configError = '';
if (!this.form.base_url.trim()) { this.configError = 'La URL base es requerida.'; return; }
diff --git a/rest/controllers/vcard_api_controller.go b/rest/controllers/vcard_api_controller.go
index 59d5dc7..10ea151 100644
--- a/rest/controllers/vcard_api_controller.go
+++ b/rest/controllers/vcard_api_controller.go
@@ -23,6 +23,45 @@ func VcardApiIndex(c *fiber.Ctx) error {
return c.Render("vcard_api", data, "layouts/main")
}
+// VcardApiLogin llama a /api/login del sistema VCard externo y devuelve el access_token.
+func VcardApiLogin(c *fiber.Ctx) error {
+ type Req struct {
+ BaseURL string `json:"base_url"`
+ Email string `json:"email"`
+ Password string `json:"password"`
+ }
+ var req Req
+ if err := c.BodyParser(&req); err != nil {
+ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"})
+ }
+ req.BaseURL = strings.TrimRight(strings.TrimSpace(req.BaseURL), "/")
+ if req.BaseURL == "" || req.Email == "" || req.Password == "" {
+ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "base_url, email y password son requeridos"})
+ }
+
+ payload, _ := json.Marshal(map[string]string{"email": req.Email, "password": req.Password})
+ client := &http.Client{Timeout: 15 * time.Second}
+ resp, err := client.Post(req.BaseURL+"/api/login", "application/json", strings.NewReader(string(payload)))
+ if err != nil {
+ return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": "No se pudo conectar: " + err.Error()})
+ }
+ defer resp.Body.Close()
+ body, _ := io.ReadAll(resp.Body)
+
+ var result map[string]interface{}
+ if err := json.Unmarshal(body, &result); err != nil {
+ return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": "Respuesta inválida del servidor"})
+ }
+ if token, ok := result["access_token"].(string); ok && token != "" {
+ return c.JSON(fiber.Map{"ok": true, "token": token})
+ }
+ msg := "Credenciales incorrectas"
+ if m, ok := result["message"].(string); ok && m != "" {
+ msg = m
+ }
+ return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": msg})
+}
+
// VcardApiSaveConfig guarda / actualiza la configuración (base URL + token).
func VcardApiSaveConfig(c *fiber.Ctx) error {
type Req struct {
diff --git a/rest/routes/user.go b/rest/routes/user.go
index 5aab020..1a133b8 100755
--- a/rest/routes/user.go
+++ b/rest/routes/user.go
@@ -217,6 +217,7 @@ func UserRoutes(app fiber.Router) {
// ─── VCard API (integración Admin Laravel) ────────────────────────────────
protected.Get("/vcard-api", middlewares.MenuMiddleware, controllers.VcardApiIndex)
+ protected.Post("/vcard-api/login", controllers.VcardApiLogin)
protected.Get("/vcard-api/config", controllers.VcardApiGetConfig)
protected.Post("/vcard-api/config", controllers.VcardApiSaveConfig)
// Usuarios (GET)