docs: update all documentation and add AI tooling configs
- Rewrite README.md with current architecture, features and stack - Update docs/API.md with all current endpoints (corporate, BI, client 360) - Update docs/ARCHITECTURE.md with cache, modular queries, services, ETL - Update docs/GUIA-USUARIO.md for all roles (admin, corporate, agente) - Add docs/INDEX.md documentation index - Add PROJETO.md comprehensive project reference - Add BI-CCC-Implementation-Guide.md - Include AI agent configs (.claude, .agents, .gemini, _bmad) - Add netbird VPN configuration - Add status report Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,430 @@
|
||||
/* eslint-disable n/no-unsupported-features/node-builtins */
|
||||
/* global document, window */
|
||||
|
||||
/**
|
||||
* PROTOTYPE DEV MODE
|
||||
*
|
||||
* Developer/feedback mode that allows users to easily copy Object IDs to clipboard
|
||||
* for providing precise feedback on prototype elements.
|
||||
*
|
||||
* Features:
|
||||
* - Toggle dev mode with button or Ctrl+E
|
||||
* - Prototype works NORMALLY when dev mode is on
|
||||
* - Hold Shift + Click any element to copy its Object ID
|
||||
* - Visual highlights show what will be copied (green when Shift is held)
|
||||
* - Tooltip shows Object ID on hover
|
||||
* - Success feedback when copied
|
||||
*
|
||||
* Usage:
|
||||
* 1. Include this script in your prototype HTML
|
||||
* 2. Add the HTML toggle button and tooltip (see HTML template)
|
||||
* 3. Add the CSS styles (see CSS template)
|
||||
* 4. Call initDevMode() on page load
|
||||
*
|
||||
* How it works:
|
||||
* - Activate dev mode (Ctrl+E or click button)
|
||||
* - Hover over elements to see their Object IDs (gray outline)
|
||||
* - Hold Shift key (outline turns green)
|
||||
* - Click while holding Shift to copy Object ID
|
||||
* - Prototype works normally without Shift held
|
||||
* - **Shift is disabled when typing in form fields** (input, textarea, etc.)
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// DEV MODE STATE
|
||||
// ============================================================================
|
||||
|
||||
let devModeActive = false;
|
||||
let shiftKeyPressed = false;
|
||||
let currentHighlightedElement = null;
|
||||
|
||||
// ============================================================================
|
||||
// INITIALIZATION
|
||||
// ============================================================================
|
||||
|
||||
function initDevMode() {
|
||||
const toggleButton = document.querySelector('#dev-mode-toggle');
|
||||
const tooltip = document.querySelector('#dev-mode-tooltip');
|
||||
|
||||
if (!toggleButton || !tooltip) {
|
||||
console.warn('⚠️ Dev Mode: Toggle button or tooltip not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user agent supports clipboard API
|
||||
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
||||
// Clipboard API available
|
||||
} else {
|
||||
console.warn('⚠️ Clipboard API not supported in this browser');
|
||||
return;
|
||||
}
|
||||
|
||||
setupKeyboardShortcuts();
|
||||
setupToggleButton(toggleButton, tooltip);
|
||||
setupHoverHighlight(tooltip);
|
||||
setupClickCopy();
|
||||
|
||||
console.log('%c💡 Dev Mode available: Press Ctrl+E or click the Dev Mode button', 'color: #0066CC; font-weight: bold;');
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// KEYBOARD SHORTCUTS
|
||||
// ============================================================================
|
||||
|
||||
function setupKeyboardShortcuts() {
|
||||
// Track Shift key for container selection
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Shift') {
|
||||
// Don't activate if user is typing in a form field
|
||||
if (isTypingInField()) {
|
||||
return;
|
||||
}
|
||||
|
||||
shiftKeyPressed = true;
|
||||
document.body.classList.add('shift-held');
|
||||
if (devModeActive) {
|
||||
console.log('%c⬆️ Shift held: Click any element to copy its Object ID', 'color: #10B981; font-weight: bold;');
|
||||
}
|
||||
}
|
||||
|
||||
// Ctrl+E toggle
|
||||
if (e.ctrlKey && e.key === 'e') {
|
||||
e.preventDefault();
|
||||
document.querySelector('#dev-mode-toggle')?.click();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keyup', (e) => {
|
||||
if (e.key === 'Shift') {
|
||||
shiftKeyPressed = false;
|
||||
document.body.classList.remove('shift-held');
|
||||
if (devModeActive) {
|
||||
console.log('%c⬇️ Shift released: Prototype works normally (hold Shift to copy)', 'color: #6b7280;');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TOGGLE BUTTON
|
||||
// ============================================================================
|
||||
|
||||
function setupToggleButton(toggleButton, tooltip) {
|
||||
toggleButton.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
if (typeof globalThis !== 'undefined') {
|
||||
globalThis.devModeActive = true;
|
||||
} else if (globalThis.window !== undefined) {
|
||||
globalThis.devModeActive = true;
|
||||
}
|
||||
devModeActive = !devModeActive;
|
||||
|
||||
// Update UI
|
||||
document.body.classList.toggle('dev-mode-active', devModeActive);
|
||||
toggleButton.classList.toggle('active', devModeActive);
|
||||
|
||||
const statusText = toggleButton.querySelector('span');
|
||||
if (statusText) {
|
||||
statusText.textContent = devModeActive ? 'Dev Mode: ON' : 'Dev Mode: OFF';
|
||||
}
|
||||
|
||||
// Log status
|
||||
console.log(`🔧 Dev Mode: ${devModeActive ? 'ACTIVATED' : 'DEACTIVATED'}`);
|
||||
|
||||
if (devModeActive) {
|
||||
console.log('%c🔧 DEV MODE ACTIVE', 'color: #0066CC; font-size: 16px; font-weight: bold;');
|
||||
console.log('%c⚠️ Hold SHIFT + Click any element to copy its Object ID', 'color: #FFB800; font-size: 14px; font-weight: bold;');
|
||||
console.log('%cWithout Shift: Prototype works normally', 'color: #6b7280;');
|
||||
console.log('%cPress Ctrl+E to toggle Dev Mode', 'color: #6b7280;');
|
||||
} else {
|
||||
tooltip.style.display = 'none';
|
||||
if (currentHighlightedElement) {
|
||||
clearHighlight();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HOVER HIGHLIGHT
|
||||
// ============================================================================
|
||||
|
||||
function setupHoverHighlight(tooltip) {
|
||||
// Show tooltip and highlight on hover
|
||||
document.addEventListener('mouseover', function (e) {
|
||||
if (!devModeActive) return;
|
||||
|
||||
// Don't highlight if user is typing in a field
|
||||
if (isTypingInField()) {
|
||||
tooltip.style.display = 'none';
|
||||
clearHighlight();
|
||||
return;
|
||||
}
|
||||
|
||||
clearHighlight();
|
||||
|
||||
let element = findElementWithId(e.target);
|
||||
|
||||
if (!element || !element.id || isSystemElement(element.id)) {
|
||||
tooltip.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
// Highlight element
|
||||
highlightElement(element, shiftKeyPressed);
|
||||
currentHighlightedElement = element;
|
||||
|
||||
// Show tooltip
|
||||
const prefix = shiftKeyPressed ? '✓ Click to Copy: ' : '⬆️ Hold Shift + Click: ';
|
||||
tooltip.textContent = prefix + element.id;
|
||||
tooltip.style.display = 'block';
|
||||
tooltip.style.background = shiftKeyPressed ? '#10B981' : '#6b7280';
|
||||
tooltip.style.color = '#fff';
|
||||
|
||||
updateTooltipPosition(e, tooltip);
|
||||
});
|
||||
|
||||
// Update tooltip position on mouse move
|
||||
document.addEventListener('mousemove', function (e) {
|
||||
if (devModeActive && tooltip.style.display === 'block') {
|
||||
updateTooltipPosition(e, tooltip);
|
||||
}
|
||||
});
|
||||
|
||||
// Clear highlight on mouse out
|
||||
document.addEventListener('mouseout', function (e) {
|
||||
if (!devModeActive) return;
|
||||
if (e.target.id) {
|
||||
tooltip.style.display = 'none';
|
||||
clearHighlight();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CLICK TO COPY
|
||||
// ============================================================================
|
||||
|
||||
function setupClickCopy() {
|
||||
// Use capture phase to intercept clicks with Shift
|
||||
document.addEventListener(
|
||||
'click',
|
||||
function (e) {
|
||||
if (!devModeActive) return;
|
||||
|
||||
// Allow toggle button to work normally
|
||||
if (isToggleButton(e.target)) return;
|
||||
|
||||
// ONLY copy if Shift is held
|
||||
if (!shiftKeyPressed) {
|
||||
// Let prototype work normally without Shift
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't intercept if user is clicking in/around a form field
|
||||
if (isTypingInField() || isFormElement(e.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Shift is held and not in a form field - intercept and copy
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
let element = findElementWithId(e.target);
|
||||
|
||||
if (!element || !element.id || isSystemElement(element.id)) {
|
||||
console.log('❌ No Object ID found');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy to clipboard
|
||||
const objectId = element.id;
|
||||
copyToClipboard(objectId);
|
||||
|
||||
// Show feedback
|
||||
showCopyFeedback(element, objectId);
|
||||
|
||||
return false;
|
||||
},
|
||||
true,
|
||||
); // Capture phase
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
function findElementWithId(element) {
|
||||
let current = element;
|
||||
let attempts = 0;
|
||||
|
||||
while (current && !current.id && attempts < 10) {
|
||||
current = current.parentElement;
|
||||
attempts++;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
function isSystemElement(id) {
|
||||
const systemIds = ['app', 'dev-mode-toggle', 'dev-mode-tooltip'];
|
||||
return systemIds.includes(id);
|
||||
}
|
||||
|
||||
function isToggleButton(element) {
|
||||
return element.id === 'dev-mode-toggle' || element.closest('#dev-mode-toggle') || element.classList.contains('dev-mode-toggle');
|
||||
}
|
||||
|
||||
function isTypingInField() {
|
||||
const activeElement = document.activeElement;
|
||||
if (!activeElement) return false;
|
||||
|
||||
const tagName = activeElement.tagName.toLowerCase();
|
||||
const isEditable = activeElement.isContentEditable;
|
||||
|
||||
// Check if user is currently typing in a form field
|
||||
return tagName === 'input' || tagName === 'textarea' || tagName === 'select' || isEditable;
|
||||
}
|
||||
|
||||
function isFormElement(element) {
|
||||
if (!element) return false;
|
||||
|
||||
const tagName = element.tagName.toLowerCase();
|
||||
const isEditable = element.isContentEditable;
|
||||
|
||||
// Check if the clicked element is a form element
|
||||
return tagName === 'input' || tagName === 'textarea' || tagName === 'select' || isEditable;
|
||||
}
|
||||
|
||||
function highlightElement(element, isShiftHeld) {
|
||||
const color = isShiftHeld ? '#10B981' : '#6b7280';
|
||||
const width = isShiftHeld ? '3px' : '2px';
|
||||
const offset = isShiftHeld ? '3px' : '2px';
|
||||
const shadowSpread = isShiftHeld ? '5px' : '2px';
|
||||
const shadowOpacity = isShiftHeld ? '0.4' : '0.2';
|
||||
|
||||
element.style.outline = `${width} solid ${color}`;
|
||||
element.style.outlineOffset = offset;
|
||||
element.style.boxShadow = `0 0 0 ${shadowSpread} rgba(${isShiftHeld ? '16, 185, 129' : '107, 114, 128'}, ${shadowOpacity})`;
|
||||
}
|
||||
|
||||
function clearHighlight() {
|
||||
if (currentHighlightedElement) {
|
||||
currentHighlightedElement.style.outline = '';
|
||||
currentHighlightedElement.style.boxShadow = '';
|
||||
currentHighlightedElement = null;
|
||||
}
|
||||
}
|
||||
|
||||
function updateTooltipPosition(e, tooltip) {
|
||||
const offset = 15;
|
||||
let x = e.clientX + offset;
|
||||
let y = e.clientY + offset;
|
||||
|
||||
// Keep tooltip on screen
|
||||
const rect = tooltip.getBoundingClientRect();
|
||||
if (x + rect.width > window.innerWidth) {
|
||||
x = e.clientX - rect.width - offset;
|
||||
}
|
||||
if (y + rect.height > window.innerHeight) {
|
||||
y = e.clientY - rect.height - offset;
|
||||
}
|
||||
|
||||
tooltip.style.left = x + 'px';
|
||||
tooltip.style.top = y + 'px';
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
if (typeof navigator !== 'undefined' && navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard
|
||||
.writeText(text)
|
||||
.then(() => {
|
||||
console.log(`📋 Copied to clipboard: ${text}`);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Dev Mode error:', error);
|
||||
fallbackCopy(text);
|
||||
});
|
||||
} else {
|
||||
fallbackCopy(text);
|
||||
}
|
||||
}
|
||||
|
||||
function fallbackCopy(text) {
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.style.left = '-999999px';
|
||||
document.body.append(textarea);
|
||||
textarea.focus();
|
||||
textarea.select();
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
console.log(`📋 Copied (fallback): ${text}`);
|
||||
} catch (error) {
|
||||
console.error('Dev Mode error:', error);
|
||||
}
|
||||
|
||||
textarea.remove();
|
||||
}
|
||||
|
||||
function showCopyFeedback(element, objectId) {
|
||||
// Create feedback overlay
|
||||
const feedback = document.createElement('div');
|
||||
feedback.textContent = '✓ Copied: ' + objectId;
|
||||
feedback.style.cssText = `
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: #10B981;
|
||||
color: #fff;
|
||||
padding: 16px 32px;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
z-index: 100000;
|
||||
box-shadow: 0 10px 25px rgba(0,0,0,0.3);
|
||||
animation: fadeInOut 1.5s ease-in-out;
|
||||
pointer-events: none;
|
||||
`;
|
||||
|
||||
document.body.append(feedback);
|
||||
|
||||
setTimeout(() => {
|
||||
feedback.remove();
|
||||
}, 1500);
|
||||
|
||||
// Flash element
|
||||
const originalOutline = element.style.outline;
|
||||
element.style.outline = '3px solid #10B981';
|
||||
setTimeout(() => {
|
||||
element.style.outline = originalOutline;
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// Add CSS animation
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes fadeInOut {
|
||||
0% { opacity: 0; transform: translate(-50%, -50%) scale(0.9); }
|
||||
20% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
||||
80% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
||||
100% { opacity: 0; transform: translate(-50%, -50%) scale(0.9); }
|
||||
}
|
||||
`;
|
||||
document.head.append(style);
|
||||
|
||||
// ============================================================================
|
||||
// EXPORT
|
||||
// ============================================================================
|
||||
|
||||
// Make available globally
|
||||
globalThis.initDevMode = initDevMode;
|
||||
|
||||
// Export for use in other scripts
|
||||
if (typeof globalThis !== 'undefined' && globalThis.exports) {
|
||||
globalThis.exports = { initDevMode };
|
||||
}
|
||||
Reference in New Issue
Block a user