feat(404): página custom con SSI de cPanel + htaccess con Options +Includes

- 404.shtml duplicado de 404.html con SSI embedded (REQUEST_URI, REMOTE_ADDR, HTTP_USER_AGENT, etc.)
- .htaccess actualizado: ErrorDocument 404 /404.shtml, Options +Includes, AddHandler server-parsed
- cPanel deshabilita Includes por default en cada VirtualHost, hay que activarlo en el .htaccess del usuario
- Verificado online: SSI procesa correctamente, URL/UA/IP visibles en la página
This commit is contained in:
Mauri 2026-06-09 09:22:32 -03:00
parent ae56e43bb6
commit db6668793e
3 changed files with 48 additions and 38 deletions

View File

@ -20,6 +20,12 @@ cp /root/opencode/development/hostingdelsur.net/src/forms/api-htaccess "$DIST/ap
# Copy root .htaccess (HTTPS forzado, security headers)
cp /root/opencode/development/hostingdelsur.net/scripts/htaccess.conf "$DIST/.htaccess"
# cPanel recognizes 404.shtml specifically (Server-Side Includes)
# Astro generates 404.html — duplicate as .shtml for cPanel
if [ -f "$DIST/404.html" ]; then
cp "$DIST/404.html" "$DIST/404.shtml"
fi
# rsync with --delete to mirror (preserva mwp/, .well-known/, etc.)
rsync -avz --delete \
--exclude='mwp' \

View File

@ -57,4 +57,15 @@
</FilesMatch>
# No listado de directorios
Options -Indexes
Options -Indexes +Includes
# Páginas de error custom
ErrorDocument 404 /404.shtml
ErrorDocument 500 /500.shtml
# Habilitar SSI para .shtml (cPanel lo desactiva por default)
AddType text/html .shtml
AddHandler server-parsed .shtml
<IfModule mod_include.c>
Options +Includes
</IfModule>

View File

@ -1,10 +1,9 @@
---
import BaseLayout from '@/layouts/BaseLayout.astro';
import CloudDivider from '@/components/CloudDivider.astro';
---
<BaseLayout
title="404 — Ups, el server se fue a buscar yerba"
description="La página que buscás no existe. Pero tenemos un robot tomando mate para hacerte compañía."
description="La página que buscás no existe, pero tenemos un robot tomando mate para hacerte compañía."
>
<section class="relative overflow-hidden py-16 md:py-24 min-h-[80vh] flex items-center" style="background: var(--hds-bg-soft);">
<div class="absolute inset-0 particle-grid opacity-40" aria-hidden="true"></div>
@ -28,10 +27,8 @@ import CloudDivider from '@/components/CloudDivider.astro';
</filter>
</defs>
<!-- Fondo circular -->
<circle cx="240" cy="240" r="220" fill="url(#bg-grad)"/>
<!-- Patrón de cuadraditos naranja (estilo logo) alrededor -->
<g opacity="0.35">
<rect x="60" y="80" width="8" height="8" fill="#FA9F5C" rx="1"/>
<rect x="68" y="80" width="8" height="8" fill="#FA9F5C" rx="1"/>
@ -48,65 +45,49 @@ import CloudDivider from '@/components/CloudDivider.astro';
<rect x="412" y="388" width="8" height="8" fill="#FFCBA1" rx="1"/>
</g>
<!-- Sombra del robot -->
<ellipse cx="240" cy="420" rx="100" ry="10" fill="#EE7623" opacity="0.12"/>
<!-- Cuerpo (pecho) -->
<g filter="url(#soft-shadow)">
<rect x="160" y="240" width="160" height="140" rx="20" fill="#E8E2D5" stroke="#EE7623" stroke-width="3"/>
<!-- Detalle del pecho (LED) -->
<circle cx="200" cy="280" r="6" fill="#EE7623">
<animate attributeName="opacity" values="1;0.3;1" dur="1.8s" repeatCount="indefinite"/>
</circle>
<circle cx="200" cy="280" r="6" fill="#EE7623" opacity="0.3"/>
<rect x="220" y="276" width="80" height="8" rx="2" fill="#1A1612" opacity="0.15"/>
<rect x="220" y="290" width="60" height="6" rx="2" fill="#1A1612" opacity="0.1"/>
<!-- Botón de pánico -->
<circle cx="280" cy="340" r="14" fill="#FAF6EE" stroke="#EE7623" stroke-width="2"/>
<circle cx="280" cy="340" r="6" fill="#EE7623"/>
</g>
<!-- Cabeza -->
<g filter="url(#soft-shadow)">
<rect x="170" y="100" width="140" height="140" rx="24" fill="#F5EFE3" stroke="#EE7623" stroke-width="3"/>
</g>
<!-- Antena -->
<line x1="240" y1="100" x2="240" y2="68" stroke="#EE7623" stroke-width="4" stroke-linecap="round"/>
<circle cx="240" cy="60" r="8" fill="#EE7623">
<animate attributeName="r" values="6;9;6" dur="2s" repeatCount="indefinite"/>
</circle>
<!-- Ojos (cierra uno como diciendo "uy") -->
<g>
<!-- Ojo izquierdo: cerrado/apretado (guiño de "uy") -->
<path d="M 200 165 Q 210 172 220 165" stroke="#1A1612" stroke-width="4" fill="none" stroke-linecap="round"/>
<line x1="195" y1="158" x2="225" y2="155" stroke="#1A1612" stroke-width="2" stroke-linecap="round" opacity="0.5"/>
<!-- Pestaña del ojo cerrado -->
<path d="M 195 158 Q 210 168 225 158" stroke="#1A1612" stroke-width="3" fill="none" stroke-linecap="round"/>
<!-- Ojo derecho: abierto y mirando de reojo (culpable) -->
<ellipse cx="260" cy="167" rx="10" ry="12" fill="#FAF6EE" stroke="#1A1612" stroke-width="2"/>
<circle cx="263" cy="170" r="5" fill="#1A1612"/>
<circle cx="264" cy="167" r="2" fill="#FAF6EE"/>
</g>
<!-- Mejillas rosadas (apenas sonrojado) -->
<ellipse cx="190" cy="195" rx="8" ry="4" fill="#FA9F5C" opacity="0.6"/>
<ellipse cx="290" cy="195" rx="8" ry="4" fill="#FA9F5C" opacity="0.6"/>
<!-- Boca: "Oops" -->
<ellipse cx="240" cy="210" rx="14" ry="10" fill="#1A1612"/>
<ellipse cx="240" cy="212" rx="10" ry="6" fill="#FAF6EE"/>
<line x1="240" y1="205" x2="240" y2="218" stroke="#1A1612" stroke-width="1.5" opacity="0.3"/>
<!-- Brazos -->
<g filter="url(#soft-shadow)">
<!-- Brazo izquierdo: en gesto "ups" (mano levantada, como diciendo "yo no fui") -->
<rect x="120" y="180" width="24" height="80" rx="12" fill="#F5EFE3" stroke="#EE7623" stroke-width="3" transform="rotate(-25 132 220)"/>
<!-- Mano izquierda abierta -->
<circle cx="110" cy="170" r="18" fill="#F5EFE3" stroke="#EE7623" stroke-width="3"/>
<!-- Dedos abiertos -->
<line x1="92" y1="160" x2="86" y2="152" stroke="#EE7623" stroke-width="3" stroke-linecap="round"/>
<line x1="98" y1="155" x2="94" y2="145" stroke="#EE7623" stroke-width="3" stroke-linecap="round"/>
<line x1="106" y1="153" x2="104" y2="142" stroke="#EE7623" stroke-width="3" stroke-linecap="round"/>
@ -114,34 +95,24 @@ import CloudDivider from '@/components/CloudDivider.astro';
<line x1="120" y1="162" x2="124" y2="155" stroke="#EE7623" stroke-width="3" stroke-linecap="round"/>
</g>
<!-- Brazo derecho: sosteniendo el mate -->
<g filter="url(#soft-shadow)">
<rect x="336" y="220" width="24" height="80" rx="12" fill="#F5EFE3" stroke="#EE7623" stroke-width="3" transform="rotate(30 348 260)"/>
<!-- Mano -->
<circle cx="358" cy="290" r="16" fill="#F5EFE3" stroke="#EE7623" stroke-width="3"/>
</g>
<!-- MATE (en la mano derecha) -->
<g transform="translate(330 280)">
<!-- Cuerpo del mate (calabaza) -->
<ellipse cx="22" cy="28" rx="24" ry="20" fill="#1A1612"/>
<!-- Brillo del mate -->
<ellipse cx="14" cy="22" rx="6" ry="4" fill="#FAF6EE" opacity="0.2"/>
<!-- Borde superior (donde va la yerba) -->
<ellipse cx="22" cy="14" rx="20" ry="5" fill="#EE7623"/>
<ellipse cx="22" cy="13" rx="18" ry="3" fill="#FFCBA1"/>
<!-- Bombilla (pico) -->
<rect x="35" y="6" width="8" height="14" rx="2" fill="#8A8580"/>
<rect x="36" y="2" width="6" height="6" rx="1" fill="#C8C2B8"/>
<!-- Capa de yerba encima -->
<ellipse cx="22" cy="11" rx="14" ry="2" fill="#2E7D5B" opacity="0.8"/>
</g>
<!-- Vapor del mate animado -->
<g opacity="0.4">
<path d="M 380 270 Q 384 262 380 254" stroke="#8A8580" stroke-width="2" fill="none" stroke-linecap="round">
<animate attributeName="opacity" values="0;0.6;0" dur="2.5s" repeatCount="indefinite"/>
<animate attributeName="transform" values="translateY(0); translateY(-10px); translateY(0)" dur="2.5s" repeatCount="indefinite"/>
</path>
<path d="M 392 270 Q 396 262 392 254" stroke="#8A8580" stroke-width="2" fill="none" stroke-linecap="round">
<animate attributeName="opacity" values="0;0.6;0" dur="2.5s" begin="0.5s" repeatCount="indefinite"/>
@ -151,18 +122,15 @@ import CloudDivider from '@/components/CloudDivider.astro';
</path>
</g>
<!-- Piernas -->
<g filter="url(#soft-shadow)">
<rect x="195" y="380" width="30" height="50" rx="10" fill="#F5EFE3" stroke="#EE7623" stroke-width="3"/>
<rect x="255" y="380" width="30" height="50" rx="10" fill="#F5EFE3" stroke="#EE7623" stroke-width="3"/>
<!-- Pies -->
<ellipse cx="210" cy="432" rx="22" ry="8" fill="#EE7623"/>
<ellipse cx="270" cy="432" rx="22" ry="8" fill="#EE7623"/>
</g>
</svg>
</div>
<!-- Texto -->
<div class="order-1 lg:order-2 text-center lg:text-left">
<div class="inline-block">
<span class="inline-flex items-center gap-3 text-xs font-semibold tracking-[0.25em] uppercase" style="color: var(--color-hds-naranja);">
@ -190,11 +158,36 @@ import CloudDivider from '@/components/CloudDivider.astro';
</a>
</div>
<div class="mt-10 pt-6 border-t flex flex-wrap items-center justify-center lg:justify-start gap-x-6 gap-y-2 text-sm" style="border-color: var(--hds-line); color: var(--hds-fg-muted);">
<a href="/servicios/" class="hover:text-hds-naranja transition-colors">Ver servicios</a>
<a href="/planes/" class="hover:text-hds-naranja transition-colors">Ver planes</a>
<a href="/contacto/" class="hover:text-hds-naranja transition-colors">Contacto</a>
<!-- Server-side include (SSI) — cPanel pone el HTML y Apache lo procesa -->
<!--#if expr="$REMOTE_ADDR" -->
<div class="mt-10 pt-6 border-t" style="border-color: var(--hds-line);">
<p class="text-xs font-semibold tracking-[0.2em] uppercase mb-3" style="color: var(--color-hds-naranja);">
Detalles técnicos
</p>
<dl class="text-xs space-y-1.5" style="color: var(--hds-fg-muted); font-family: ui-monospace, monospace;">
<div class="flex gap-2">
<dt class="w-32 flex-shrink-0" style="color: var(--hds-fg-soft);">URL solicitada:</dt>
<dd class="break-all"><!--#echo var="REQUEST_URI" --></dd>
</div>
<div class="flex gap-2">
<dt class="w-32 flex-shrink-0" style="color: var(--hds-fg-soft);">URL de referencia:</dt>
<dd class="break-all"><!--#echo var="HTTP_REFERER" --></dd>
</div>
<div class="flex gap-2">
<dt class="w-32 flex-shrink-0" style="color: var(--hds-fg-soft);">IP del visitante:</dt>
<dd><!--#echo var="REMOTE_ADDR" --></dd>
</div>
<div class="flex gap-2">
<dt class="w-32 flex-shrink-0" style="color: var(--hds-fg-soft);">Navegador:</dt>
<dd class="break-all"><!--#echo var="HTTP_USER_AGENT" --></dd>
</div>
<div class="flex gap-2">
<dt class="w-32 flex-shrink-0" style="color: var(--hds-fg-soft);">Servidor:</dt>
<dd><!--#echo var="SERVER_NAME" --></dd>
</div>
</dl>
</div>
<!--#endif -->
</div>
</div>
</div>