Applied

Engineering

Results

samueltorres.dev

Ciudad de México

Ciudad de México, México Disponible de inmediato

Disponibilidad

Proyectos remotos y presenciales MXN · USD

Documentación

Cómo está construido y cómo se mantiene samueltorres.dev — estructura, código, formulario de contacto, SEO, servidor y publicación, explicado paso a paso.

Última actualización: 25 de septiembre de 2026.

Resumen

samueltorres.dev es el portafolio de Samuel Torres Pichardo. Es un sitio estático y bilingüe (español / inglés) construido sobre la plantilla HTML Ashley. No usa framework ni proceso de compilación para las páginas: cada página es un archivo .html escrito a mano que el servidor entrega tal cual.

Las únicas piezas que se mueven son:

  • SCSS, que se compila en local a un solo css/style.css (con Prepros).
  • JavaScript en el navegador: transiciones entre páginas, animaciones, sliders, el selector de idioma y el formulario de contacto.
  • PHP en el servidor, solo para mail.php (formulario de contacto) y, opcionalmente, para la analítica propia.
DatoValor
Dominiohttps://samueltorres.dev (sin www; el www redirige aquí)
Home en español/ (el archivo index.html)
Home en inglés/home-1.html
HostingcPanel compartido, Apache + PHP 8.3, certificado AutoSSL gratuito
Carpeta públicapublic_html/
Cómo se suben los archivosA mano, con el Administrador de archivos (File Manager) de cPanel
Sobre esta página: es una referencia interna. Lleva noindex, nofollow, está fuera de sitemap.xml y ningún menú la enlaza, así que ni los buscadores ni los visitantes normales llegan a ella.

Estructura del proyecto

Todo vive en una sola carpeta. Las páginas HTML están en la raíz y los recursos y el código del servidor en subcarpetas. La columna ¿Se sube? indica si el elemento va al servidor.

RutaQué contiene¿Se sube?
*.html (raíz)Todas las páginas del sitio, en inglés y en españolSí
css/style.css (compilado) y las hojas de los plugins en css/plugins/Sí
js/Los scripts del sitio y las librerías en js/plugins/Sí
img/Fotos, capturas de proyectos, iconos y la portada para redes (og-cover.jpg)Sí
docs/certificates/PDF e imágenes de certificados que se abren desde la página de certificacionesSí (solo esta subcarpeta de docs/)
mail.phpBackend del formulario de contactoSí
.htaccessReglas de Apache: HTTPS, redirecciones, cabeceras, cachéSí, con cuidado
robots.txt, sitemap.xmlInstrucciones y lista de páginas para los buscadoresSí
favicon*, apple-touch-icon.png, site.webmanifestIconos de la pestaña del navegador y de la pantalla de inicioSí
api/, admin/, cron/Analítica propia (ver su sección)Solo si se activa la analítica
config/Credenciales de la base de datos (db.php)Solo con analítica, nunca a git
scss/Código fuente de los estilosNo
sql/, tools/Esquema de la base de datos, script para crear el administrador, generador del sitemapNo
*.bak, *.bak2, *.zipRespaldos localesNunca (cualquiera podría descargarlos por URL)
*.md, contexto-completo.txt, DEPLOY-PENDIENTE.txtNotas internasNo

Páginas e idiomas

Cada página existe dos veces, una por idioma, como dos archivos separados. No hay motor de traducción: cada archivo ya trae su texto definitivo.

Convención de nombres
  • El inglés lleva el nombre base: contact.html, cv.html, case-study-bea.html…
  • El español agrega -es: contact-es.html, cv-es.html, case-study-bea-es.html…
  • La única excepción es el home: el home en español es index.html (se sirve en /) y su gemelo en inglés es home-1.html. El viejo home-1-es.html está retirado y redirige a /.
  • 404.html es la única página sin gemela.
Grupos de páginas
GrupoArchivos (nombre en inglés)
Homeindex.html (ES) / home-1.html (EN)
Proyectosportfolio-1.html, project-*.html (6), case-study-*.html (13)
Serviciosservices.html, service.html, service-ai, -cloud, -linux, -security
Perfilcv.html, certifications.html
Contacto y legalcontact.html, privacy.html
Internasdocumentation.html (esta página), 404.html
Selector de idioma ES · EN

initLangSwitch(), en js/main.js, agrega el botón ES · EN a la barra superior de todas las páginas. Deduce la página gemela a partir del nombre del archivo (con la convención y la excepción del home de arriba) y simplemente enlaza a ella. Guarda la elección en localStorage, pero nunca redirige por su cuenta: quien comparte un enlace en español siempre recibe la página en español. En 404.html el botón aparece deshabilitado.

Para agregar una página nueva: crea los dos archivos (nombre.html y nombre-es.html), pon <html lang="en"> / lang="es", agrega a ambos el canonical y las tres etiquetas hreflang (en, es, x-default) y regenera el sitemap (ver SEO). El selector de idioma encontrará la gemela solo.

Marcado HTML

Todas las páginas siguen el mismo esqueleto. Si sabes qué hace cada bloque, puedes editar cualquier página sin romper la plantilla:

<!DOCTYPE html>
<html lang="es"> <!-- o "en" en la gemela en inglés -->

<head>
    <!-- meta, descripción, canonical, hreflang, favicons, CSS -->
</head>

<body>

    <!-- wrapper -->
    <div class="mil-wrapper" id="top">

        <!-- cursor -->
        <div class="mil-ball"> ... </div>

        <!-- preloader -->
        <div class="mil-preloader"> ... </div>

        <!-- barra de progreso -->
        <div class="mil-progress-track"> ... </div>

        <!-- menú -->
        <div class="mil-menu-frame"> ... </div>

        <!-- cortina -->
        <div class="mil-curtain"></div>

        <!-- marco -->
        <div class="mil-frame"> ... </div>

        <!-- contenido -->
        <div class="mil-content">
            <div id="swupMain" class="mil-main-transition">

                <!-- banner --> ...
                <!-- secciones de la página --> ...
                <!-- footer --> ...
                <!-- elementos ocultos --> ...

            </div>
        </div>

    </div>
    <!-- fin del wrapper -->

    <!-- scripts: plugins, luego js/main.js, luego los de la página -->

</body>
</html>
BloqueQué hace
.mil-ballEl cursor circular propio que sigue al mouse (solo en escritorio). Sus etiquetas "More" / "Choose" aparecen sobre enlaces y sliders.
.mil-preloaderAnimación de entrada mientras carga la página (ver Preloader).
.mil-progress-trackLa barra delgada que indica cuánto has bajado en la página.
.mil-menu-frameEl menú de pantalla completa que se abre con el botón de hamburguesa. Los enlaces están en #swupMenu.
.mil-curtainLa capa que cubre la pantalla durante la transición entre páginas.
.mil-frameBarra superior fija (logo, selector de idioma, botón de menú) e inferior (página actual, "Subir").
#swupMainLa única parte que cambia entre páginas. Swup reemplaza solo este contenedor, así que todo lo de fuera se queda en su lugar.
.mil-hidden-elementsSVG decorativos (flechas, líneas, dodecaedro) que main.js copia a la página donde hacen falta.
Clases útiles
  • mil-up: el elemento aparece y sube al entrar en pantalla (GSAP ScrollTrigger).
  • mil-mb-30, mil-mb-60, mil-p-120-90…: ayudas de espaciado (margen inferior, relleno de sección) en píxeles.
  • mil-dark-bg, mil-muted, mil-light-soft, mil-thin: tonos de fondo y de texto.

CSS y SCSS

Las páginas cargan cuatro hojas de estilo de plugins y la hoja propia del sitio, siempre en este orden:

<!-- bootstrap grid css -->
<link rel="stylesheet" href="/css/plugins/bootstrap-grid.css">
<!-- font awesome css -->
<link rel="stylesheet" href="/css/plugins/font-awesome.min.css">
<!-- swiper css -->
<link rel="stylesheet" href="/css/plugins/swiper.min.css">
<!-- fancybox css -->
<link rel="stylesheet" href="/css/plugins/fancybox.min.css">
<!-- ashley scss -->
<link rel="stylesheet" href="/css/style.css">
Plugins
  • Bootstrap grid: solo el sistema de rejilla (container, row, col-lg-*), no el resto de Bootstrap.
  • Font Awesome: iconos.
  • Swiper: estilos de los sliders.
  • Fancybox: estilos del visor ampliado (imágenes y certificados).
Estilos propios (SCSS)

css/style.css es un archivo generado: no lo edites a mano. Edita los archivos de scss/ y compila con Prepros (el archivo prepros.config de la raíz ya trae la configuración). scss/style.scss es la entrada que importa a los demás:

ArchivoQué contiene
_variables.scssColores, fuentes (aquí se importa Outfit) y valores compartidos. Si cambias un color aquí, cambia en todo el sitio.
_common.scssBase: tipografía, enlaces, botones, formularios, migas de pan, fondos, espaciados.
_components.scssPiezas de la plantilla: preloader, cursor, menú, banner, servicios, footer, portafolio, acordeón, 404, transiciones, selector de idioma…
_recognition.scssEl bloque de reconocimientos del home.
_certifications.scssLa página de certificaciones.
_project-preview.scssLa rejilla de proyectos y sus vistas previas.
_documentation.scssEsta página: índice lateral, bloques de código, botón de copiar.

JavaScript

Todas las páginas cargan las mismas librerías y después js/main.js. Al final van solo los scripts que esa página necesita.

<!-- jQuery js -->
<script src="/js/plugins/jquery.min.js"></script>
<!-- swup js -->
<script src="/js/plugins/swup.min.js"></script>
<!-- swiper js -->
<script src="/js/plugins/swiper.min.js"></script>
<!-- fancybox js -->
<script src="/js/plugins/fancybox.min.js"></script>
<!-- gsap js -->
<script src="/js/plugins/gsap.min.js"></script>
<!-- scroll smoother -->
<script src="/js/plugins/smooth-scroll.js"></script>
<!-- scroll trigger js -->
<script src="/js/plugins/ScrollTrigger.min.js"></script>
<!-- scroll to js -->
<script src="/js/plugins/ScrollTo.min.js"></script>
<!-- ashley js -->
<script src="/js/main.js"></script>
<!-- scripts de la página (solo donde hacen falta) -->
<script src="/js/contact-form.js"></script>
Librerías
LibreríaPara qué se usa
jQueryBase de main.js y de Fancybox.
SwupTransiciones suaves entre páginas sin recargar todo.
SwiperSliders (proyectos, testimonios, galerías).
FancyboxVisor ampliado de imágenes y certificados.
GSAP + ScrollTrigger + ScrollToAnimaciones: preloader, aparición al hacer scroll, barra de progreso, saltos suaves a anclas.
Smooth scrollDesplazamiento más suave con la rueda del mouse.
Scripts del sitio
ArchivoSe carga enQué hace
main.jsTodas las páginasSwup, selector de idioma, preloader, cursor, menú, barra de progreso, animaciones de scroll, sliders y Fancybox.
contact-form.jsContactoEnvía el formulario a mail.php sin recargar y muestra el mensaje de resultado.
certifications.jsHome, certificacionesConfigura el visor Fancybox para los certificados.
recognition.jsHomeEl bloque interactivo de reconocimientos.
documentation.jsEsta páginaResalta la sección actual en el índice y hace funcionar los botones "Copiar".
track.jsNinguna todavíaAnalítica propia, lista pero sin activar (ver su sección).
i18n.js, translations.jsNingunaMotor de traducción antiguo, ya sin uso. Se conservan en disco solo como referencia.
Importante al programar: con Swup la página no se recarga de verdad, solo se reemplaza #swupMain. Por eso un script propio de una página tiene que volver a arrancar en el evento swup:contentReplaced y limpiar lo suyo (listeners, temporizadores, ScrollTriggers) en swup:willReplaceContent. La sección "REINIT" al final de main.js y los archivos recognition.js y documentation.js muestran cómo.

Preloader

Cada página arranca con una animación breve sobre fondo negro, en dos etapas, que GSAP ejecuta desde la sección "preloader" de js/main.js:

  1. Tres palabras (.mil-animation-1) aparecen y se desvanecen una tras otra.
  2. El dominio (.mil-animation-2): un bloque de color lo barre y revela samueltorres.dev.
  3. La pantalla se desvanece, recibe la clase mil-hidden y la página queda lista para usarse.
<div class="mil-preloader">
    <div class="mil-preloader-animation">
        <div class="mil-pos-abs mil-animation-1">
            <p class="mil-h3 mil-muted mil-thin">Ingeniería</p>
            <p class="mil-h3 mil-muted">Aplicada</p>
            <p class="mil-h3 mil-muted mil-thin">Resultados</p>
        </div>
        <div class="mil-pos-abs mil-animation-2">
            <div class="mil-reveal-frame">
                <p class="mil-reveal-box"></p>
                <p class="mil-h3 mil-muted mil-thin">samueltorres.dev</p>
            </div>
        </div>
    </div>
</div>
Para cambiar el texto: es HTML normal, así que se edita directo. No hay letras separadas que reconstruir. El bloque está copiado en cada una de las 64 páginas, así que el cambio hay que hacerlo en todos los .html (un buscar y reemplazar en la carpeta basta) y volver a subirlos todos. Las páginas en inglés usan "Applied / Engineering / Results".

Formulario de contacto

El formulario de contact.html / contact-es.html envía un correo a la bandeja de Samuel. Participan tres piezas:

  1. HTML: <form id="mil-contact-form" action="/mail.php" method="POST"> con los campos name, email, message y un campo oculto antibots, website. Los atributos data-msg-* guardan los mensajes que ve el visitante en cada idioma.
  2. js/contact-form.js: revisa que nada esté vacío, desactiva el botón ("Enviando…"), manda los datos con fetch más la cabecera X-Requested-With: XMLHttpRequest y luego muestra el mensaje de éxito o de error.
  3. mail.php: vuelve a validar todo en el servidor y envía el correo con la función mail() de PHP.
Revisiones de mail.php, en orden
#RevisiónSi falla
1La petición es POST405 method_not_allowed
2Trae la cabecera X-Requested-With403 forbidden
3Viene de samueltorres.dev o www.samueltorres.dev (Origin/Referer, lista en ALLOWED_ORIGINS)403 forbidden
4Esa IP no lleva 3 envíos en los últimos 15 minutos429 rate_limited
5El campo oculto website está vacío (un bot lo llenaría)Responde "éxito" pero no envía nada
6Nombre, correo y mensaje están llenos400 missing_fields
7El correo tiene un formato válido400 invalid_email
8El servidor logra enviar el correo500 mail_failed

Si todo pasa, la respuesta es {"success": true}. El correo sale como [email protected] con la dirección del visitante en Reply-To, así que al pulsar "Responder" en Gmail le contestas directo al visitante. El destinatario está escrito en el código del servidor y nunca viene del navegador, por lo que nadie puede usar el formulario para mandar correos a otra persona.

Si no llegan los correos:
  1. Revisa la carpeta de Spam.
  2. En cPanel, entra a Correo electrónico → Email Deliverability y confirma que SPF y DKIM de samueltorres.dev aparezcan como válidos. Sin ellos, Gmail suele rechazar o esconder los correos de no-reply@.
  3. Usa Correo electrónico → Track Delivery para ver si el servidor lo envió y qué contestó el servidor de destino.
  4. Si el visitante ve "Algo salió mal", PHP no pudo enviar el correo. Revisa el archivo error_log de public_html.
  5. Al hacer pruebas, recuerda el límite de 3 intentos cada 15 minutos por IP, que también cuenta los intentos fallidos.

Analítica propia

El sitio trae su propio contador de visitas, sin Google Analytics ni ningún otro tercero. Está construido pero no activado: todavía ninguna página carga track.js. Tiene cuatro partes:

ParteQué hace
js/track.jsManda una sola petición por página vista (página, título, de dónde viene, idioma) a /api/collect.php. Sin cookies ni localStorage, y no hace nada si el navegador tiene activado "No rastrear".
api/collect.phpSolo acepta peticiones del propio dominio y guarda la visita en MySQL (tablas sessions y visits).
admin/Panel con contraseña: resumen, visitas, páginas más vistas y sesiones.
cron/purge.phpBorra los datos de más de 90 días. Se ejecuta desde el cron del hosting, nunca desde el navegador.

Privacidad: la dirección IP nunca se guarda. El servidor solo conserva una huella de un solo sentido (SHA-256 de IP + navegador + la fecha del día) que cambia a diario: sirve para agrupar las páginas de una misma visita, pero no permite identificar a nadie ni seguirlo de un día a otro. Los pasos completos para activarla (base de datos, config/db.php, primer administrador, cron) están en README-analytics.md.

SEO

  • canonical: cada página declara su propia URL definitiva, para que Google no trate las variantes como duplicados.
  • hreflang: cada página apunta a su gemela en inglés, a su gemela en español y a un x-default. Así Google sabe qué versión mostrarle a cada visitante.
  • Open Graph: el título, la descripción y la imagen (img/og-cover.jpg) que se usan al compartir un enlace en LinkedIn, WhatsApp y apps parecidas. Hoy está en los home, el portafolio, los casos de estudio y certificaciones.
  • sitemap.xml: la lista de las 60 URL públicas con sus pares de idioma. No lo edites a mano. Regenéralo con php tools/build-sitemap.php desde la raíz del proyecto. El script omite 404.html, documentation*.html y el retirado home-1-es.html.
  • robots.txt: permite todo el sitio, bloquea /admin/, /api/, /config/, /sql/, /docs/, /cron/ y /tools/, y apunta al sitemap.

Servidor (.htaccess)

El archivo .htaccess de la raíz le dice a Apache cómo servir el sitio:

ReglaEfecto
HTTPSToda petición http:// se redirige (301) a https://.
Sin wwwwww.samueltorres.dev se redirige (301) a samueltorres.dev.
Home antiguo/home-1-es.html se redirige (301) a /.
Página de errorCualquier URL que no exista muestra 404.html.
Sin listado de carpetasOptions -Indexes: nadie puede ver el contenido de una carpeta.
Cabeceras de seguridadnosniff, X-Frame-Options: SAMEORIGIN (el sitio no se puede incrustar en otro) y Referrer-Policy.
CachéEl HTML nunca se guarda en caché (los cambios se ven al momento). Imágenes, CSS y JS se guardan 1 año.
Compresióngzip / Brotli si el servidor los soporta.
Ojo con la caché de 1 año: si cambias css/style.css o un .js y lo subes, los visitantes que ya tenían el anterior pueden seguir viéndolo. Para forzar la actualización, cambia la referencia en el HTML (por ejemplo style.css?v=2). Para revisar tus propios cambios, recarga con Ctrl+F5.
Carpeta compartida: el public_html de la cuenta de hosting también contiene otros sitios en subcarpetas, y estos heredan este .htaccess. Por eso cada regla va dentro de <IfModule> y apunta a rutas concretas. Después de cambiarlo, prueba todo el sitio y ten a la mano la copia anterior por si hay que revertir.

Publicación

Los cambios se publican a mano: se suben los archivos con el Administrador de archivos de cPanel a public_html, respetando la misma estructura de carpetas del proyecto.

  1. Haz y revisa los cambios en local (abre las páginas en el navegador).
  2. Si tocaste SCSS, compila con Prepros para actualizar css/style.css.
  3. Si agregaste o quitaste páginas, regenera sitemap.xml.
  4. Anota los archivos modificados en DEPLOY-PENDIENTE.txt (una lista local con el formato archivo → subir a public_html/carpeta/; no se sube al servidor ni a git).
  5. Sube cada archivo a su carpeta dentro de public_html, reemplazando el anterior.
  6. Revisa el sitio en vivo: las páginas editadas, el selector ES · EN y, si tocaste mail.php, manda una prueba desde el formulario.
Nunca subas: *.bak, *.bak2, *.zip, config/ (salvo con analítica), sql/, tools/, scss/, *.md, contexto-completo.txt ni nada de docs/ que no sea docs/certificates/. El servidor los entrega como texto o como descarga a cualquiera que adivine la URL. Si alguno ya está en el servidor, bórralo.

Fuentes

  • Outfit (pesos 100–900), de Google Fonts. Se importa en scss/_variables.scss y se usa en todo el sitio.
  • Los bloques de código de esta página usan la fuente monoespaciada del sistema (Courier New).

Iconos

  • Font Awesome 5: iconos de redes sociales, sobre, flechas. Uso: <i class="fab fa-linkedin"></i> (marcas) o <i class="fas fa-chevron-down"></i> (sólidos).
  • SVG propios de la plantilla: la flecha del cursor, las líneas decorativas y las flechas de los botones (en .mil-hidden-elements).
  • Iconos del sitio: favicon.ico, favicon-16x16.png, favicon-32x32.png, apple-touch-icon.png e img/icon-192.png / icon-512.png (declarados en site.webmanifest).

English version

Contacto profesional:

Ciudad de México

Ciudad de México, México Disponible de inmediato

Disponibilidad

Proyectos remotos y presenciales MXN · USD

© 2026 Ing. Samuel Torres. Applied Engineering.