diff --git a/CHANGELOG.md b/CHANGELOG.md
index 83ce103..ed71648 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog - project.linkedcorp.net
+## [2.13.1] - 2026-10-14
+### Fixed
+- ComboBox y Flyout (menu de 3 puntos): el panel desplegable se quedaba cortado por el borde
+ inferior/derecho de la ventana cuando el trigger estaba cerca de ese borde (ej. compartir una
+ credencial, "Eliminar" en una fila al final de la grilla). Ahora se mide el tamaño real del
+ panel y se voltea hacia arriba / se recorta hacia adentro si no cabe — siempre visible dentro
+ de la ventana.
+
## [2.13.0] - 2026-10-14
### Added
- Contraseñas: grupos de un solo nivel (ej. "Correos") para organizar las credenciales — se
diff --git a/VERSION b/VERSION
index fb2c076..94f15e9 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.13.0
+2.13.1
diff --git a/backend/package.json b/backend/package.json
index 348014a..2b809ac 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -1,6 +1,6 @@
{
"name": "project-linkedcorp-backend",
- "version": "2.13.0",
+ "version": "2.13.1",
"private": true,
"type": "module",
"main": "src/server.js",
diff --git a/frontend/package.json b/frontend/package.json
index ffffbe3..01b6d14 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,7 +1,7 @@
{
"name": "project-linkedcorp-frontend",
"private": true,
- "version": "2.13.0",
+ "version": "2.13.1",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/frontend/src/components/ComboBox.jsx b/frontend/src/components/ComboBox.jsx
index e7bf68a..7cb1315 100644
--- a/frontend/src/components/ComboBox.jsx
+++ b/frontend/src/components/ComboBox.jsx
@@ -1,4 +1,4 @@
-import { useEffect, useRef, useState } from 'react';
+import { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { ChevronDown, Search } from 'lucide-react';
@@ -10,16 +10,40 @@ import { ChevronDown, Search } from 'lucide-react';
export function ComboBox({ options, value, onChange, placeholder = '', searchThreshold = 10 }) {
const [open, setOpen] = useState(false);
const [query, setQuery] = useState('');
- const [pos, setPos] = useState(null);
+ const [pos, setPos] = useState(null); // { top, left, width, anchorTop, anchorBottom, ready }
const triggerRef = useRef(null);
const panelRef = useRef(null);
function openPanel() {
const rect = triggerRef.current.getBoundingClientRect();
- setPos({ top: rect.bottom + 4, left: rect.left, width: rect.width });
+ setPos({
+ top: rect.bottom + 4, left: rect.left, width: rect.width,
+ anchorTop: rect.top, anchorBottom: rect.bottom, ready: false,
+ });
setOpen(true);
}
+ // Fase 2 (igual tecnica que Flyout): con el panel ya en el DOM (oculto) se mide su alto real y,
+ // si no cabe hacia abajo, se voltea hacia arriba del trigger -- nunca se deja cortado por el
+ // borde inferior del viewport (ver project-md-templates/leeme_primero.txt, seccion UI/UX).
+ useLayoutEffect(() => {
+ if (!open || !pos || pos.ready || !panelRef.current) return;
+ const panelRect = panelRef.current.getBoundingClientRect();
+ const margin = 8;
+
+ let top = pos.top;
+ if (pos.anchorBottom + 4 + panelRect.height > window.innerHeight - margin) {
+ const openUpTop = pos.anchorTop - 4 - panelRect.height;
+ top = openUpTop >= margin ? openUpTop : Math.max(margin, window.innerHeight - margin - panelRect.height);
+ }
+
+ let left = pos.left;
+ if (left + pos.width > window.innerWidth - margin) left = window.innerWidth - margin - pos.width;
+ if (left < margin) left = margin;
+
+ setPos((p) => ({ ...p, top, left, ready: true }));
+ }, [open, pos]);
+
useEffect(() => {
if (!open) return;
function onDocMouseDown(e) {
@@ -57,7 +81,7 @@ export function ComboBox({ options, value, onChange, placeholder = '', searchThr