1. Descripcion General del Sistema
| Atributo | Valor |
| Nombre | PhysCalc Pro |
| Tipo | Aplicacion web estatica (SPA-like) |
| Proposito | Calculadora de propiedades fisicas industriales con 7 modulos |
| Cliente | TecnoIndustria S.A.S. |
| Usuarios objetivo | Ingenieros, tecnicos y operarios de planta |
| Backend | Ninguno (100% client-side) |
| Base de datos | Ninguna |
| Dependencias externas | Ninguna (cero frameworks, cero librerias) |
| Compatibilidad | Chrome 80+, Firefox 75+, Edge 80+, Safari 13+ |
2. Stack Tecnologico
| Capa | Tecnologia | Version | Justificacion |
| Estructura | HTML5 | 5 | Semantico, accesible, estandar web |
| Estilos | CSS3 | 3 | Variables CSS, grid, flexbox, animaciones nativas |
| Logica | JavaScript | ES6+ | Vanilla JS sin dependencias para maxima portabilidad |
| Hosting | GitHub Pages | - | Gratuito, HTTPS, CDN global, despliegue desde repositorio |
| Control de versiones | Git | 2.x+ | Versionado estandar de la industria |
Decisiones de diseno: Se eligio Vanilla JS (sin React, Vue, Angular ni jQuery) para
garantizar: cero dependencias, carga instantanea, facil mantenimiento y portabilidad total. El proyecto
funciona abriendo el HTML directamente desde el sistema de archivos.
3. Estructura de Archivos
PhysCalc-Pro/
|
|-- index.html # Landing page / blog del proyecto
|-- app.html # Aplicacion de calculadora (7 modulos)
|-- manual-usuario.html # Manual de usuario
|-- manual-tecnico.html # Manual tecnico (este archivo)
|
|-- css/
| |-- style.css # Estilos principales (app + blog)
| |-- manual.css # Estilos de los manuales
|
|-- js/
| |-- app.js # Logica completa de la calculadora
|
|-- img/ # Directorio para imagenes (vacio por defecto)
|
|-- README.md # Documentacion del repositorio
Tamano de archivos
| Archivo | Lineas aprox. | Funcion |
| app.html | ~400 | Estructura de los 7 paneles + historial |
| js/app.js | ~490 | Config de formulas, motor generico, validacion, historial |
| css/style.css | ~650 | Todos los estilos (blog + app + responsive) |
| index.html | ~220 | Blog / landing page |
4. Arquitectura del Software
El sistema sigue un patron Config-Driven donde la logica de cada formula se define
como datos, no como codigo imperativo.
+--------------------------+
| app.html | Capa de Presentacion
| (Estructura + Paneles) | - HTML semantico
+--------------------------+ - Selectores, inputs, botones
|
v
+--------------------------+
| style.css | Capa de Estilos
| (Visual + Responsive) | - Variables CSS
+--------------------------+ - Grid/Flexbox responsive
| - Estados (error, target, disabled)
v
+--------------------------+
| app.js | Capa de Logica
| |
| +--------------------+ |
| | FORMULAS (config) | | Configuracion declarativa:
| | - variables | | - Variables con restricciones
| | - solve functions | | - Funciones de calculo
| | - denominators | | - Denominadores para div/0
| | - labels | | - Labels de botones
| +--------------------+ |
| | |
| +--------------------+ |
| | Motor generico | | Funciones genericas:
| | - onSolveForChange | | - Cambio de variable objetivo
| | - validateFormula | | - Validacion en tiempo real
| | - calcFormula | | - Calculo y resultado
| +--------------------+ |
| | |
| +--------------------+ |
| | Utilidades | | Funciones compartidas:
| | - Field errors | | - Manejo de errores en campos
| | - History | | - Historial de calculos
| | - Tabs | | - Navegacion por pestanas
| +--------------------+ |
+--------------------------+
5. Capa de Presentacion (HTML)
Estructura de un panel de calculo
Cada panel sigue esta estructura estandar:
<div class="calc-panel" id="panel-{modulo}">
<h2>Titulo del modulo</h2>
<div class="formula-display">Formula</div>
<p class="description">Descripcion detallada...</p>
<!-- Selector de variable a resolver -->
<div class="solve-for-group">
<label>Resolver para:</label>
<select id="{modulo}-solve" class="solve-for-select">
<option value="var1">...</option>
<option value="var2">...</option>
</select>
</div>
<!-- Campos para TODAS las variables -->
<div class="form-group">
<label for="{id}">Variable (simbolo) - unidad</label>
<input type="number" id="{id}" step="any">
<small>Descripcion y restricciones</small>
</div>
<button class="btn-calc" onclick="calcFormula('panel-{modulo}')">
Calcular {variable}
</button>
<div class="result-box" aria-live="polite">
<div class="result-label"></div>
<div class="result-value"></div>
<div class="result-detail"></div>
</div>
</div>
Convencion de IDs
| Elemento | Patron de ID | Ejemplo |
| Panel | panel-{modulo} | panel-calor |
| Selector resolver | {modulo}-solve | calor-solve |
| Input de variable | {modulo}-{var} | calor-m, calor-q |
6. Capa de Estilos (CSS)
Variables CSS (custom properties)
:root {
--primary: #0d6efd; /* Azul principal */
--primary-dark: #0a58ca; /* Azul hover */
--accent: #00c853; /* Verde acentuado */
--dark: #1a1a2e; /* Fondo oscuro */
--dark2: #16213e; /* Fondo oscuro secundario */
--light: #f8f9fa; /* Fondo claro */
--gray: #6c757d; /* Texto secundario */
--white: #ffffff;
--shadow: 0 4px 20px rgba(0,0,0,0.1);
--radius: 12px;
}
Estados de los inputs
| Estado | Clase CSS | Visual |
| Normal | (ninguna) | Borde gris #e0e0e0, fondo #fafafa |
| Enfocado | :focus | Borde azul, fondo blanco |
| Error | .input-error | Borde rojo #e53935, fondo #fff5f5, animacion shake |
| Campo objetivo | .solve-target | Borde verde punteado, fondo verde claro, readonly |
| Boton deshabilitado | :disabled | Fondo gris #b0bec5, cursor not-allowed |
Responsive breakpoints
| Breakpoint | Cambio |
768px | Navbar se convierte en menu hamburguesa |
500px | Input-row pasa de 2 columnas a 1 columna |
7. Capa de Logica (JavaScript)
Funciones utilitarias
| Funcion | Parametros | Retorno | Descripcion |
getVal(id) | ID del input | number | null | Lee y parsea el valor de un input. Retorna null si vacio o NaN. |
isEmpty(id) | ID del input | boolean | Verifica si el campo esta vacio. |
formatNum(n) | Numero | string | Formatea a max 4 decimales, elimina ceros finales. |
setFieldError(id, msg) | ID, mensaje | void | Agrega clase .input-error y mensaje de error al campo. |
clearFieldError(id) | ID | void | Remueve error visual y mensaje del campo. |
setBtn(panelId, enabled, text) | Panel, estado, texto | void | Habilita/deshabilita boton y cambia su texto. |
showResult(...) | Panel, label, value, unit, detail | void | Muestra resultado en la caja verde. |
Funciones del motor generico
| Funcion | Descripcion |
onSolveForChange(panelId) | Se ejecuta al cambiar el selector "Resolver para". Configura readonly en el campo objetivo, restaura defaults en los demas, actualiza la formula mostrada y el texto del boton. |
validateFormula(panelId) | Valida todos los campos de entrada (excepto el objetivo) en tiempo real. Aplica restricciones de positive, notZero, min, max y denominador. Habilita/deshabilita el boton. |
calcFormula(panelId) | Lee los valores, ejecuta la funcion solve correspondiente, escribe el resultado en el campo objetivo y en la result-box, y guarda en el historial. |
Funciones del historial
| Funcion | Descripcion |
addToHistory(module, formula, result, detail) | Agrega una entrada al inicio del array (max 10). |
renderHistory() | Renderiza el HTML del historial. |
clearHistory() | Vacia el array y re-renderiza. |
8. Sistema de Configuracion de Formulas
Cada modulo de calculo esta definido como un objeto en FORMULAS:
FORMULAS['panel-calor'] = {
name: 'Calor', // Nombre para historial
selectId: 'calor-solve', // ID del <select> "Resolver para"
variables: {
Q: {
id: 'calor-q', // ID del input
label: 'Calor (Q)', // Nombre para UI
unit: 'J', // Unidad
positive: false, // ¿Debe ser >= 0?
notZero: false, // ¿No puede ser 0?
default: null, // Valor por defecto (null = sin default)
min: undefined, // Valor minimo
max: undefined // Valor maximo
},
m: { ... },
c: { ... },
dT: { ... }
},
rearranged: { // Formula mostrada segun variable objetivo
Q: 'Q = m · c · ΔT',
m: 'm = Q / (c · ΔT)',
c: 'c = Q / (m · ΔT)',
dT: 'ΔT = Q / (m · c)'
},
solve: { // Funciones de calculo
Q: (v) => v.m * v.c * v.dT,
m: (v) => v.Q / (v.c * v.dT),
c: (v) => v.Q / (v.m * v.dT),
dT: (v) => v.Q / (v.m * v.c)
},
btnLabels: { // Texto del boton segun variable
Q: 'Calcular Calor',
m: 'Calcular Masa', ...
},
denominators: { // Variables que son denominador
Q: [], // Q = m*c*dT (no divide)
m: ['c', 'dT'], // m = Q/(c*dT) -> c y dT no pueden ser 0
c: ['m', 'dT'],
dT: ['m', 'c']
},
// Opcionales:
postValidate: (target, vals) => { ... }, // Validacion extra
postCalc: (target, result, vals) => { ... } // Info extra post-calculo
};
Propiedades de variables
| Propiedad | Tipo | Descripcion |
id | string | ID del elemento input en el DOM |
label | string | Nombre visible para resultados e historial |
unit | string | Unidad de medida |
positive | boolean | Si true, no acepta valores negativos |
notZero | boolean | Si true, no acepta valor 0 |
default | string|null | Valor precargado (ej: '4186' para agua, '9.8' para gravedad) |
min | number|undefined | Valor minimo permitido |
max | number|undefined | Valor maximo permitido |
9. Sistema de Validacion
Capas de validacion
| Capa | Donde | Que valida |
| 1. HTML | Atributos min, max, step | Primera barrera nativa del navegador |
| 2. Tiempo real (JS) | validateFormula() en eventos input/change | Restricciones fisicas por campo individual |
| 3. Contexto (JS) | Verificacion de denominadores | Division por cero segun la variable objetivo |
| 4. Post-validacion | postValidate() opcional | Reglas entre multiples campos (ej: Eutil ≤ Etotal) |
| 5. Post-calculo | Verificacion de isFinite() | Resultado no es Infinity ni NaN |
Flujo de validacion
Usuario escribe en un input
|
v
[evento 'input' dispara validateFormula(panelId)]
|
v
Para cada variable (excepto la objetivo):
- ¿Campo vacio? -> no marcar error, allValid = false
- ¿Valor null/NaN? -> error "Valor no valido"
- ¿positive && val < 0? -> error "No puede ser negativo"
- ¿notZero && val === 0? -> error "No puede ser cero"
- ¿val < min? -> error "Minimo: {min}"
- ¿val > max? -> error "Maximo: {max}"
- ¿Es denominador && val === 0? -> error "No puede ser cero (division)"
- Todo OK -> clearFieldError()
|
v
[postValidate() si existe] -> validacion cruzada
|
v
setBtn(panelId, allValid) -> habilitar/deshabilitar boton
Principio: Los campos vacios NO se marcan con error (el usuario aun no ha interactuado).
Solo se marcan cuando el usuario ha escrito un valor invalido.
10. Flujo de Datos Completo
[Carga de pagina]
|
v
DOMContentLoaded -> initAll()
|
+--> initTabs() -- Configura pestanas
+--> initRealTimeValidation -- Bind eventos input/change
| |
| +--> Para cada panelId en FORMULAS:
| +--> Bind input/change en cada variable -> validateFormula()
| +--> Bind change en selector -> onSolveForChange()
| +--> onSolveForChange() inicial (estado por defecto)
|
+--> renderHistory() -- Historial vacio
[Usuario cambia "Resolver para:"]
|
v
onSolveForChange(panelId)
|
+--> Actualiza formula-display con formula reorganizada
+--> Campo objetivo: readonly, placeholder "= ?", clase .solve-target
+--> Otros campos: editables, restaurar defaults
+--> Actualiza texto del boton
+--> Oculta resultado previo
+--> Llama validateFormula() para actualizar estado del boton
[Usuario escribe en un campo]
|
v
validateFormula(panelId)
|
+--> Valida cada campo -> setFieldError() o clearFieldError()
+--> setBtn(panelId, allValid)
[Usuario presiona "Calcular"]
|
v
calcFormula(panelId)
|
+--> Lee valores de inputs
+--> Ejecuta FORMULAS[panelId].solve[target](vals)
+--> Verifica isFinite(result)
+--> Escribe resultado en campo objetivo
+--> Muestra en result-box
+--> addToHistory() -> renderHistory()
11. Despliegue en GitHub Pages
Requisitos previos
- Cuenta de GitHub
- Git instalado localmente
Pasos de despliegue
# 1. Crear repositorio en GitHub (nombre: PhysCalc-Pro)
# 2. Vincular repositorio local
cd C:/Users/Ardisa/TecnoIndustria
git remote add origin https://github.com/TU-USUARIO/PhysCalc-Pro.git
git branch -M main
git push -u origin main
# 3. Configurar GitHub Pages
# - Ir a Settings > Pages
# - Source: Deploy from a branch
# - Branch: main / (root)
# - Save
# 4. Esperar 1-2 minutos. URL sera:
# https://TU-USUARIO.github.io/PhysCalc-Pro/
Actualizaciones
# Hacer cambios en los archivos, luego:
git add -A
git commit -m "Descripcion del cambio"
git push
# GitHub Pages se actualiza automaticamente en 1-2 minutos
12. Consideraciones de Seguridad
| Aspecto | Estado | Detalle |
| XSS | Protegido | No se usa innerHTML con datos del usuario. El historial usa datos calculados internamente, no input directo. |
| Inyeccion SQL | N/A | No hay base de datos. |
| HTTPS | Si (GitHub Pages) | Certificado SSL gratuito incluido. |
| Datos sensibles | Ninguno | No se recopilan datos personales. Todo se procesa en el navegador. |
| Cookies | Ninguna | No se usan cookies ni almacenamiento local. |
| eval() | No se usa | Las formulas se ejecutan como funciones JavaScript nativas. |
13. Rendimiento
| Metrica | Valor estimado |
| Tamano total (sin imagenes) | ~35 KB (sin comprimir) |
| Requests HTTP | 3 (HTML + CSS + JS) |
| Tiempo de carga | < 1 segundo |
| Dependencias externas | 0 |
| Frameworks | 0 |
| Tiempo de calculo | < 1 ms por operacion |
| Funciona offline | Si (una vez cargada) |
14. Accesibilidad
aria-live="polite" en las cajas de resultado para lectores de pantalla.
aria-label en selectores de formula.
- Todos los campos tienen
<label> asociado via for.
- Contraste de colores cumple WCAG AA.
- Navegacion completa por teclado (Tab, Enter, flechas).
- Elementos interactivos nativos (
button, input, select).
- Responsive desde 320px.
15. Guia de Mantenimiento
Cambiar un valor por defecto
Editar la propiedad default en el objeto FORMULAS en js/app.js.
Cambiar restricciones de un campo
Modificar las propiedades positive, notZero, min, max del campo en FORMULAS.
Cambiar colores
Editar las variables CSS en :root de css/style.css.
Cambiar clasificacion de eficiencia
Modificar la funcion postCalc en FORMULAS['panel-eficiencia'].
16. Como Agregar un Nuevo Modulo
Pasos para agregar una nueva formula (ej: Ley de Ohm V = I · R):
Paso 1: Agregar pestana en app.html
<button class="calc-tab" data-target="panel-ohm">Ley de Ohm</button>
Paso 2: Agregar panel HTML en app.html
<div class="calc-panel" id="panel-ohm">
<h2>Ley de Ohm</h2>
<div class="formula-display">V = I · R</div>
<p class="description">...</p>
<div class="solve-for-group">
<label for="ohm-solve">Resolver para:</label>
<select id="ohm-solve" class="solve-for-select">
<option value="V">V - Voltaje (V)</option>
<option value="I">I - Corriente (A)</option>
<option value="R">R - Resistencia (Ω)</option>
</select>
</div>
<div class="form-group">
<label for="ohm-v">Voltaje (V) - en Voltios (V)</label>
<input type="number" id="ohm-v" step="any">
</div>
<!-- ... campos para I y R ... -->
<button class="btn-calc" onclick="calcFormula('panel-ohm')" disabled>
Calcular
</button>
<div class="result-box" aria-live="polite">...</div>
</div>
Paso 3: Agregar configuracion en js/app.js
FORMULAS['panel-ohm'] = {
name: 'Ley de Ohm',
selectId: 'ohm-solve',
variables: {
V: { id: 'ohm-v', label: 'Voltaje (V)', unit: 'V',
positive: true, notZero: false, default: null },
I: { id: 'ohm-i', label: 'Corriente (I)', unit: 'A',
positive: true, notZero: true, default: null },
R: { id: 'ohm-r', label: 'Resistencia (R)', unit: 'Ω',
positive: true, notZero: true, default: null }
},
rearranged: { V: 'V = I · R', I: 'I = V / R', R: 'R = V / I' },
solve: {
V: v => v.I * v.R,
I: v => v.V / v.R,
R: v => v.V / v.I
},
btnLabels: { V: 'Calcular Voltaje', I: 'Calcular Corriente',
R: 'Calcular Resistencia' },
denominators: { V: [], I: ['R'], R: ['I'] }
};
Eso es todo. No se necesita modificar ninguna funcion. El motor generico detecta automaticamente
el nuevo modulo en el Object.keys(FORMULAS) al inicializar. La validacion, el cambio de
variable objetivo, el calculo y el historial funcionan sin codigo adicional.