docs: Plan P2603.01 Automatización Zoom y actualizaciones P2601.09

This commit is contained in:
Ricardo Monla
2026-03-31 18:03:21 -03:00
parent 160da47f96
commit 1fd213173e
72 changed files with 19694 additions and 460 deletions
@@ -165,10 +165,11 @@ function App() {
// Doc viewer modal
const [docModal, setDocModal] = useState<{ show: boolean; title: string; content: string; loading: boolean }>({ show: false, title: '', content: '', loading: false });
const openDocModal = React.useCallback(async (proyecto: string, archivo: string) => {
const openDocModal = React.useCallback(async (fileRelPath: string) => {
const archivo = fileRelPath.split('/').pop() || '';
setDocModal({ show: true, title: archivo.replace('.md', ''), content: '', loading: true });
try {
const res = await fetch(`${API_URL}/docs/${proyecto}/${archivo}`);
const res = await fetch(`${API_URL}/docs/${fileRelPath}`);
const data = await res.json();
if (data.content) {
setDocModal({ show: true, title: archivo.replace('.md', ''), content: data.content, loading: false });
@@ -185,11 +186,12 @@ function App() {
a: ({ node, ...props }: any) => {
const url = props.href || '';
// Si es un link a un .md en ../proy/, abrir modal de documento
if (url.includes('../proy/') && url.endsWith('.md')) {
const parts = url.split('/');
const archivo = parts.pop() || '';
const proyecto = parts.pop() || '';
// Si es un link a un .md en ../proy/ o ../ambito/, abrir modal de documento
if ((url.includes('../proy/') || url.includes('../ambito/')) && url.endsWith('.md')) {
// Obtener todo lo que viene después de "docs/" basándonos en si fue proy/ o ambito/
const linkType = url.includes('../proy/') ? '../proy/' : '../ambito/';
const docsIndex = url.indexOf(linkType) + 3; // +3 skips "../" so we start at "proy/" or "ambito/"
const fileRelPath = url.substring(docsIndex); // ex: proy/P2601_Dasuten/P26...
return (
<a
@@ -198,10 +200,10 @@ function App() {
onClick={(e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
openDocModal(proyecto, archivo);
openDocModal(fileRelPath);
}}
className="desc-link"
title={`Ver ${archivo} en la web`}
title={`Ver ${url.split('/').pop()} en la web`}
style={{ cursor: 'pointer', borderBottom: '1px dashed var(--primary)' }}
>
{props.children}
@@ -879,23 +881,29 @@ function App() {
{/* Agrupar entradas por ÁMBITO en lugar de por nodo */}
{(() => {
// Agrupar por ámbito
const porAmbito = {};
// Agrupar por ámbito a nivel de cada entrada
const porAmbito: Record<string, any> = {};
bitacora?.nodos?.forEach(nodoGroup => {
const ambito = nodoGroup.ambito_nombre || 'Sin Ámbito';
if (!porAmbito[ambito]) {
porAmbito[ambito] = {
ambito: ambito,
nodos: new Set(),
entradas: [],
resumenes: []
};
}
porAmbito[ambito].entradas.push(...nodoGroup.entradas);
porAmbito[ambito].nodos.add(nodoGroup.nodo);
if (nodoGroup.resumen) {
porAmbito[ambito].resumenes.push({ nodo: nodoGroup.nodo, resumen: nodoGroup.resumen });
// Si el nodo tiene resumen genérico, lo guardamos en su primer ámbito disponible o "Global"
const defaultAmbito = nodoGroup.entradas.length > 0 ? (nodoGroup.entradas[0].ambito_nombre || nodoGroup.ambito_nombre || 'Sin Ámbito') : 'Sin Ámbito';
if (!porAmbito[defaultAmbito]) porAmbito[defaultAmbito] = { ambito: defaultAmbito, nodos: new Set(), entradas: [], resumenes: [] };
porAmbito[defaultAmbito].resumenes.push({ nodo: nodoGroup.nodo, resumen: nodoGroup.resumen });
}
nodoGroup.entradas.forEach(e => {
const ambito = e.ambito_nombre || nodoGroup.ambito_nombre || 'Sin Ámbito';
if (!porAmbito[ambito]) {
porAmbito[ambito] = {
ambito: ambito,
nodos: new Set(),
entradas: [],
resumenes: []
};
}
porAmbito[ambito].entradas.push(e);
porAmbito[ambito].nodos.add(nodoGroup.nodo || e.nodo_nombre);
});
});
return Object.values(porAmbito).map((ambitoGroup, idx) => (