feat(pacifictrans-go): plataforma web oficial en subdominio pacifictrans.almeira.com.co con Dockerfile y despliegue Git en Coolify

This commit is contained in:
AI Bot 2026-10-13 18:49:54 -05:00
parent 0cb63165cb
commit 8d5ee782ae
10 changed files with 1554 additions and 4 deletions

View File

@ -5,7 +5,7 @@ const config: CapacitorConfig = {
appName: 'Pacifictrans Go',
webDir: 'public',
server: {
url: 'https://transporte.almeira.com.co',
url: 'https://pacifictrans.almeira.com.co',
cleartext: true,
androidScheme: 'https',
},

View File

@ -0,0 +1,39 @@
# Dockerfile oficial para Pacifictrans Go (Web Platform & Microservices API)
FROM node:20-alpine AS builder
WORKDIR /app
# Copiar manifiestos e instalar dependencias
COPY package.json package-lock.json* ./
RUN npm ci --include=dev
# Copiar código fuente y compilar TypeScript a JavaScript
COPY tsconfig.json ./
COPY src ./src
RUN npm run build
# Imagen de producción final optimizada
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=4000
# Instalar únicamente dependencias de producción
COPY package.json package-lock.json* ./
RUN npm ci --omit=dev && npm cache clean --force
# Copiar distribución compilada y frontend web estático
COPY --from=builder /app/dist ./dist
COPY public ./public
# Crear usuario del sistema para ejecución segura
RUN addgroup -S pacifictrans && adduser -S pacifictrans -G pacifictrans
RUN chown -R pacifictrans:pacifictrans /app
USER pacifictrans
EXPOSE 4000
CMD ["node", "dist/index.js"]

View File

@ -0,0 +1,684 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@500;600;700;800;900&display=swap');
:root {
--bg-primary: #080c14;
--bg-secondary: #0f172a;
--bg-card: rgba(15, 23, 42, 0.75);
--bg-card-hover: rgba(30, 41, 59, 0.85);
--border-glass: rgba(255, 255, 255, 0.08);
--border-focus: rgba(6, 182, 212, 0.5);
--accent-cyan: #06b6d4;
--accent-cyan-glow: rgba(6, 182, 212, 0.35);
--accent-emerald: #10b981;
--accent-emerald-glow: rgba(16, 185, 129, 0.3);
--accent-amber: #f59e0b;
--accent-amber-glow: rgba(245, 158, 11, 0.35);
--accent-purple: #8b5cf6;
--text-main: #f8fafc;
--text-muted: #94a3b8;
--text-dim: #64748b;
--radius-sm: 8px;
--radius-md: 14px;
--radius-lg: 22px;
--radius-full: 9999px;
--shadow-card: 0 10px 30px -5px rgba(0, 0, 0, 0.5);
--shadow-glow: 0 0 25px var(--accent-cyan-glow);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background-color: var(--bg-primary);
color: var(--text-main);
line-height: 1.6;
overflow-x: hidden;
background-image:
radial-gradient(circle at 15% 20%, rgba(6, 182, 212, 0.08) 0%, transparent 40%),
radial-gradient(circle at 85% 60%, rgba(245, 158, 11, 0.06) 0%, transparent 45%),
radial-gradient(circle at 50% 90%, rgba(16, 185, 129, 0.07) 0%, transparent 50%);
background-attachment: fixed;
}
h1, h2, h3, h4, .brand-title {
font-family: 'Outfit', sans-serif;
letter-spacing: -0.02em;
}
/* NAVBAR */
.navbar {
position: sticky;
top: 0;
z-index: 100;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
background: rgba(8, 12, 20, 0.85);
border-bottom: 1px solid var(--border-glass);
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.brand {
display: flex;
align-items: center;
gap: 0.75rem;
text-decoration: none;
}
.brand-badge {
background: linear-gradient(135deg, var(--accent-cyan), var(--accent-emerald));
color: #000;
font-weight: 900;
font-size: 1.1rem;
width: 42px;
height: 42px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 20px var(--accent-cyan-glow);
}
.brand-text h1 {
font-size: 1.35rem;
color: #fff;
line-height: 1.1;
display: flex;
align-items: center;
gap: 0.4rem;
}
.brand-text span {
font-size: 0.72rem;
color: var(--accent-amber);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.nav-links {
display: flex;
gap: 1.5rem;
align-items: center;
}
.nav-links a {
color: var(--text-muted);
text-decoration: none;
font-weight: 500;
font-size: 0.95rem;
transition: all 0.2s ease;
}
.nav-links a:hover, .nav-links a.active {
color: var(--accent-cyan);
}
.btn-apk-header {
background: linear-gradient(135deg, #22c55e, #10b981);
color: #052e16;
font-weight: 700;
padding: 0.6rem 1.25rem;
border-radius: var(--radius-full);
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
font-size: 0.9rem;
transition: all 0.25s ease;
box-shadow: 0 4px 15px rgba(34, 197, 94, 0.3);
}
.btn-apk-header:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(34, 197, 94, 0.5);
}
/* HERO SECTION */
.hero {
max-width: 1280px;
margin: 0 auto;
padding: 3.5rem 1.5rem 2rem;
display: grid;
grid-template-columns: 1.1fr 0.9fr;
gap: 3rem;
align-items: center;
}
.hero-pill {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.35rem 0.9rem;
background: rgba(6, 182, 212, 0.1);
border: 1px solid rgba(6, 182, 212, 0.3);
border-radius: var(--radius-full);
color: var(--accent-cyan);
font-size: 0.8rem;
font-weight: 600;
margin-bottom: 1.25rem;
}
.hero-title {
font-size: 3.2rem;
line-height: 1.15;
color: #fff;
margin-bottom: 1.25rem;
}
.hero-title span {
background: linear-gradient(135deg, var(--accent-cyan), var(--accent-amber));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.hero-desc {
font-size: 1.15rem;
color: var(--text-muted);
margin-bottom: 2rem;
max-width: 600px;
}
.hero-badges {
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin-bottom: 2rem;
}
.badge-item {
display: flex;
align-items: center;
gap: 0.5rem;
background: var(--bg-card);
padding: 0.5rem 1rem;
border-radius: var(--radius-md);
border: 1px solid var(--border-glass);
font-size: 0.88rem;
}
.badge-item svg {
color: var(--accent-emerald);
}
/* HERO CARD VISUAL */
.hero-visual {
position: relative;
}
.visual-container {
position: relative;
border-radius: var(--radius-lg);
overflow: hidden;
border: 1px solid var(--border-glass);
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6);
}
.hero-image {
width: 100%;
height: 480px;
object-fit: cover;
display: block;
filter: brightness(0.95);
}
.floating-pool-card {
position: absolute;
bottom: 20px;
left: 20px;
right: 20px;
background: rgba(15, 23, 42, 0.88);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: var(--radius-md);
padding: 1.25rem;
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}
.pool-status-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}
.pool-seats {
display: flex;
gap: 0.4rem;
}
.seat-dot {
width: 22px;
height: 22px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: 700;
background: #334155;
color: #94a3b8;
transition: all 0.3s ease;
}
.seat-dot.active {
background: var(--accent-emerald);
color: #022c22;
box-shadow: 0 0 12px var(--accent-emerald-glow);
}
.seat-dot.pending {
background: var(--accent-amber);
color: #451a03;
animation: pulse-seat 1.5s infinite;
}
@keyframes pulse-seat {
0% { transform: scale(1); }
50% { transform: scale(1.08); }
100% { transform: scale(1); }
}
/* SECTION TABS INTERACTION */
.main-wrapper {
max-width: 1280px;
margin: 0 auto;
padding: 2rem 1.5rem 5rem;
}
.tab-nav {
display: flex;
gap: 0.75rem;
background: var(--bg-secondary);
padding: 0.4rem;
border-radius: var(--radius-md);
border: 1px solid var(--border-glass);
margin-bottom: 2.5rem;
max-width: 820px;
margin-left: auto;
margin-right: auto;
overflow-x: auto;
}
.tab-btn {
flex: 1;
background: transparent;
border: none;
color: var(--text-muted);
padding: 0.75rem 1rem;
border-radius: var(--radius-sm);
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
transition: all 0.25s ease;
white-space: nowrap;
}
.tab-btn:hover {
color: #fff;
background: rgba(255, 255, 255, 0.04);
}
.tab-btn.active {
background: linear-gradient(135deg, rgba(6, 182, 212, 0.2), rgba(16, 185, 129, 0.2));
color: #fff;
border: 1px solid rgba(6, 182, 212, 0.4);
box-shadow: 0 4px 15px rgba(6, 182, 212, 0.15);
}
/* TAB PANELS */
.tab-content {
display: none;
animation: fadeIn 0.35s ease forwards;
}
.tab-content.active {
display: block;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* GLASS CONTAINER CARDS */
.glass-panel {
background: var(--bg-card);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid var(--border-glass);
border-radius: var(--radius-lg);
padding: 2.5rem;
box-shadow: var(--shadow-card);
}
.panel-header {
margin-bottom: 2rem;
text-align: center;
}
.panel-header h2 {
font-size: 2rem;
color: #fff;
margin-bottom: 0.5rem;
}
.panel-header p {
color: var(--text-muted);
max-width: 600px;
margin: 0 auto;
}
/* BOOKING GRID */
.grid-2col {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2.5rem;
}
.form-group {
margin-bottom: 1.25rem;
}
.form-label {
display: block;
font-size: 0.85rem;
font-weight: 600;
color: var(--text-muted);
margin-bottom: 0.45rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.form-input, .form-select {
width: 100%;
padding: 0.85rem 1.15rem;
background: rgba(15, 23, 42, 0.9);
border: 1px solid var(--border-glass);
border-radius: var(--radius-sm);
color: #fff;
font-size: 0.98rem;
font-family: inherit;
transition: all 0.2s ease;
}
.form-input:focus, .form-select:focus {
outline: none;
border-color: var(--accent-cyan);
box-shadow: 0 0 12px var(--accent-cyan-glow);
}
/* TRIP SUMMARY & BOLD PREVIEW */
.summary-box {
background: rgba(11, 17, 32, 0.8);
border: 1px solid var(--border-glass);
border-radius: var(--radius-md);
padding: 1.75rem;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.summary-item {
display: flex;
justify-content: space-between;
padding: 0.65rem 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.summary-item:last-child {
border-bottom: none;
}
.summary-total {
margin-top: 1.25rem;
padding-top: 1.25rem;
border-top: 1px solid rgba(255, 255, 255, 0.15);
display: flex;
justify-content: space-between;
align-items: baseline;
}
.total-price {
font-size: 2.2rem;
font-weight: 800;
font-family: 'Outfit', sans-serif;
color: var(--accent-emerald);
}
.bold-gateway-badge {
display: flex;
align-items: center;
gap: 0.5rem;
background: rgba(245, 158, 11, 0.1);
border: 1px solid rgba(245, 158, 11, 0.25);
border-radius: var(--radius-sm);
padding: 0.5rem 0.75rem;
margin: 1rem 0;
font-size: 0.8rem;
color: var(--accent-amber);
}
.btn-primary {
width: 100%;
background: linear-gradient(135deg, var(--accent-cyan), var(--accent-emerald));
color: #042f2e;
border: none;
font-weight: 700;
font-size: 1.05rem;
padding: 1rem;
border-radius: var(--radius-sm);
cursor: pointer;
transition: all 0.25s ease;
box-shadow: 0 4px 20px rgba(6, 182, 212, 0.3);
display: flex;
align-items: center;
justify-content: center;
gap: 0.6rem;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 25px rgba(6, 182, 212, 0.5);
}
/* RADAR VISUALIZER */
.radar-screen {
background: #050810;
border: 1px solid var(--border-glass);
border-radius: var(--radius-md);
height: 380px;
position: relative;
overflow: hidden;
margin-bottom: 2rem;
}
.radar-grid {
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(6, 182, 212, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(6, 182, 212, 0.05) 1px, transparent 1px);
background-size: 40px 40px;
}
.radar-sweep {
position: absolute;
width: 200%;
height: 200%;
top: -50%;
left: -50%;
background: conic-gradient(from 0deg, transparent 0deg, rgba(6, 182, 212, 0.15) 60deg, transparent 61deg);
animation: radar-spin 6s linear infinite;
pointer-events: none;
}
@keyframes radar-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.corridor-line {
position: absolute;
top: 50%;
left: 10%;
right: 10%;
height: 4px;
background: linear-gradient(90deg, #10b981 0%, #06b6d4 50%, #f59e0b 100%);
border-radius: 2px;
box-shadow: 0 0 15px rgba(6, 182, 212, 0.6);
}
.corridor-point {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
width: 14px;
height: 14px;
border-radius: 50%;
border: 2px solid #fff;
}
.corridor-point.cali {
left: 10%;
background: #10b981;
}
.corridor-point.dagua {
left: 35%;
background: #06b6d4;
}
.corridor-point.lobo {
left: 65%;
background: #06b6d4;
}
.corridor-point.buena {
left: 90%;
background: #f59e0b;
}
.radar-vehicle {
position: absolute;
width: 32px;
height: 32px;
border-radius: 50%;
background: rgba(6, 182, 212, 0.2);
border: 1px solid var(--accent-cyan);
display: flex;
align-items: center;
justify-content: center;
font-size: 0.9rem;
box-shadow: 0 0 15px var(--accent-cyan-glow);
cursor: pointer;
transition: transform 0.2s ease;
}
.radar-vehicle:hover {
transform: scale(1.3);
z-index: 10;
}
/* DRIVER RESIDENCE CARD */
.driver-card {
background: rgba(15, 23, 42, 0.85);
border: 1px solid var(--border-glass);
border-radius: var(--radius-sm);
padding: 1.25rem;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
transition: all 0.2s ease;
}
.driver-card:hover {
border-color: var(--accent-cyan);
background: rgba(30, 41, 59, 0.85);
}
.driver-tag {
display: inline-block;
font-size: 0.72rem;
padding: 0.2rem 0.5rem;
border-radius: 4px;
background: rgba(16, 185, 129, 0.15);
color: var(--accent-emerald);
font-weight: 600;
margin-top: 0.25rem;
}
/* DOWNLOAD SECTION */
.download-card {
background: linear-gradient(135deg, rgba(15, 23, 42, 0.95), rgba(30, 41, 59, 0.8));
border: 1px solid rgba(34, 197, 94, 0.3);
border-radius: var(--radius-lg);
padding: 3rem;
display: flex;
align-items: center;
justify-content: space-between;
gap: 2rem;
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.5);
margin-top: 3rem;
}
.btn-apk-large {
background: linear-gradient(135deg, #22c55e, #10b981);
color: #052e16;
font-weight: 800;
font-size: 1.15rem;
padding: 1.1rem 2rem;
border-radius: var(--radius-full);
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.75rem;
box-shadow: 0 6px 25px rgba(34, 197, 94, 0.4);
transition: all 0.25s ease;
}
.btn-apk-large:hover {
transform: translateY(-3px);
box-shadow: 0 10px 30px rgba(34, 197, 94, 0.6);
}
/* FOOTER */
.footer {
border-top: 1px solid var(--border-glass);
padding: 3rem 2rem 2rem;
background: #050810;
color: var(--text-dim);
font-size: 0.88rem;
text-align: center;
}
/* RESPONSIVE */
@media (max-width: 900px) {
.hero {
grid-template-columns: 1fr;
padding-top: 2rem;
}
.grid-2col {
grid-template-columns: 1fr;
}
.download-card {
flex-direction: column;
text-align: center;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 KiB

View File

@ -0,0 +1,491 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pacifictrans Go | Transporte Especial & Mensajería Cali - Buenaventura</title>
<meta name="description" content="Plataforma tecnológica de transporte especial intermunicipal y mensajería en el corredor Cali - Buenaventura. Agrupación inteligente de 4 pasajeros, Waze TSP y pagos 100% seguros con Bold.">
<meta property="og:title" content="Pacifictrans Go - Corredor Cali ↔ Buenaventura">
<meta property="og:description" content="Servicio puerta a puerta de 4 cupos y paquetería express. Monitoreo satelital y conductores asignados por cercanía de residencia.">
<meta property="og:type" content="website">
<meta name="theme-color" content="#080c14">
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<!-- NAVBAR -->
<header class="navbar">
<a href="#" class="brand">
<div class="brand-badge">P</div>
<div class="brand-text">
<h1 class="brand-title">Pacifictrans <span>Go</span></h1>
<span>Cali ↔ Buenaventura</span>
</div>
</a>
<nav class="nav-links">
<a href="#reservas" class="active">Pasajeros (Pooling)</a>
<a href="#fletes">Fletes & Encomiendas</a>
<a href="#radar">Radar de Flota</a>
<a href="#conductores">Conductores</a>
<a href="/PacifictransGo.apk" class="btn-apk-header" id="btn-download-header">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<path d="M17.523 15.3414c-.5511 0-.9993-.4486-.9993-1.0001s.4482-1.0001.9993-1.0001c.552 0 1.0001.4486 1.0001 1.0001s-.4481 1.0001-1.0001 1.0001m-11.046 0c-.5511 0-.9993-.4486-.9993-1.0001s.4482-1.0001.9993-1.0001c.552 0 1.0001.4486 1.0001 1.0001s-.4481 1.0001-1.0001 1.0001m11.4045-6.02l1.997-3.4589c.1408-.2439.0573-.5552-.1866-.696-.2438-.1408-.5551-.0573-.696.1866l-2.0238 3.5054c-1.5037-.6873-3.1979-1.0772-4.9691-1.0772s-3.4654.3899-4.9691 1.0772l-2.0238-3.5054c-.1409-.2439-.4522-.3274-.696-.1866-.2439.1408-.3274.4521-.1866.696l1.997 3.4589c-3.7997 2.0623-6.375 5.9863-6.446 10.5369h24.646c-.071-4.5506-2.6463-8.4746-6.446-10.5369"/>
</svg>
Descargar APK Android
</a>
</nav>
</header>
<!-- HERO SECTION -->
<main>
<section class="hero">
<div class="hero-content">
<div class="hero-pill">
<span>⚡ Corredor Exclusivo Especial</span>
<span>•</span>
<span>100% Prepagado con Bold & Bre-B</span>
</div>
<h2 class="hero-title">Transporte Puerta a Puerta y Mensajería Express <span>Cali - Buenaventura</span></h2>
<p class="hero-desc">
Viajes agrupados inteligentemente en vehículos de 4 pasajeros. Salidas puntuales, geolocalización satelital en tiempo real, optimización de rutas TSP con Waze y asignación de conductores según cercanía de su residencia.
</p>
<div class="hero-badges">
<div class="badge-item">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
</svg>
<span><strong>4 Cupos</strong> por Vehículo</span>
</div>
<div class="badge-item">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
</svg>
<span><strong>$35.000 COP</strong> Tarifa Fija</span>
</div>
<div class="badge-item">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
</svg>
<span>Seguro Contractual & Bold</span>
</div>
</div>
<div style="display: flex; gap: 1rem;">
<a href="#reservas" class="btn-primary" style="width: auto; padding: 0.9rem 1.8rem;">
Reservar Mi Cupo Ahora
</a>
<a href="#fletes" class="btn-primary" style="width: auto; padding: 0.9rem 1.8rem; background: rgba(255,255,255,0.06); color: #fff; border: 1px solid var(--border-glass);">
Cotizar Flete de Encomienda
</a>
</div>
</div>
<div class="hero-visual">
<div class="visual-container">
<img src="/images/hero-banner.jpg" alt="Pacifictrans Go Cali Buenaventura" class="hero-image">
<div class="floating-pool-card">
<div class="pool-status-row">
<div>
<span style="font-size: 0.75rem; text-transform: uppercase; color: var(--accent-cyan); font-weight: 700; letter-spacing: 0.05em;">Agrupación en Vivo</span>
<div style="font-weight: 700; font-size: 1.05rem;" id="seat-counter-text">3/4 Pasajeros Confirmados</div>
</div>
<div class="pool-seats">
<div class="seat-dot active">1</div>
<div class="seat-dot active">2</div>
<div class="seat-dot active">3</div>
<div class="seat-dot pending">4</div>
</div>
</div>
<p style="font-size: 0.82rem; color: var(--text-muted); line-height: 1.4;">
📍 Recogida en Cali: <strong>Puerta a puerta en su domicilio</strong>.<br>
🏁 Llegada en Buenaventura: <strong>Terminal, Muelle Turístico o El Piñal</strong>.
</p>
</div>
</div>
</div>
</section>
<!-- MAIN PLATFORM APPS / TABS WRAPPER -->
<div class="main-wrapper" id="reservas">
<div class="tab-nav">
<button class="tab-btn active" data-tab="tab-pasajeros" id="tab-btn-pasajeros">
👥 1. Reserva Pasajeros (Geo-Pooling)
</button>
<button class="tab-btn" data-tab="tab-fletes" id="tab-btn-fletes">
📦 2. Mensajería & Fletes
</button>
<button class="tab-btn" data-tab="tab-radar" id="tab-btn-radar">
📡 3. Radar de Flota & Despacho
</button>
<button class="tab-btn" data-tab="tab-conductores" id="tab-btn-conductores">
🚗 4. Portal Conductor
</button>
</div>
<!-- TAB 1: RESERVA PASAJEROS -->
<section class="tab-content active" id="tab-pasajeros">
<div class="glass-panel">
<div class="panel-header">
<h2>Agrupación Geoespacial de 4 Pasajeros</h2>
<p>El sistema agrupa automáticamente a 4 pasajeros dentro de un radio de 4 km en Cali y optimiza la ruta mediante algoritmo Nearest Neighbor TSP en Waze.</p>
</div>
<div class="grid-2col">
<!-- Formulario de Reserva -->
<div>
<div class="form-group">
<label class="form-label" for="pool-origin">Sentido del Viaje</label>
<select class="form-select" id="pool-origin">
<option value="CALI">Cali ➔ Buenaventura (Recogida Domicilio)</option>
<option value="BUENAVENTURA">Buenaventura ➔ Cali (Puntos Oficiales)</option>
</select>
</div>
<div class="form-group" id="cali-address-group">
<label class="form-label" for="cali-address">Dirección de Recogida en Cali (Domicilio)</label>
<input type="text" class="form-input" id="cali-address" placeholder="Ej: Calle 5 #66-20, Barrio Limonar, Cali" value="Calle 9 #42-15, Los Cámbulos, Cali">
</div>
<div class="form-group" id="buena-stop-group">
<label class="form-label" for="buena-stop">Parada de Destino en Buenaventura</label>
<select class="form-select" id="buena-stop">
<option value="TERMINAL">Terminal de Transporte de Buenaventura</option>
<option value="MUELLE">Muelle Turístico</option>
<option value="PINAL">El Piñal</option>
<option value="SENA">Sena Buenaventura</option>
</select>
</div>
<div class="grid-2col" style="gap: 1rem; margin-bottom: 0;">
<div class="form-group">
<label class="form-label" for="user-name">Nombre Completo</label>
<input type="text" class="form-input" id="user-name" placeholder="Tu Nombre" value="Andrea Montoya">
</div>
<div class="form-group">
<label class="form-label" for="user-phone">WhatsApp (+57)</label>
<input type="tel" class="form-input" id="user-phone" placeholder="3167455877" value="3167455877">
</div>
</div>
<div class="form-group">
<label class="form-label" for="user-notes">Notas / Equipaje</label>
<input type="text" class="form-input" id="user-notes" placeholder="Maletín mediano de 10kg">
</div>
</div>
<!-- Resumen y Pasarela Bold -->
<div class="summary-box">
<div>
<h3 style="font-size: 1.25rem; color: #fff; margin-bottom: 1rem;">Resumen de la Reserva</h3>
<div class="summary-item">
<span style="color: var(--text-muted);">Servicio:</span>
<span style="color: #fff; font-weight: 600;">Transporte Especial Intermunicipal</span>
</div>
<div class="summary-item">
<span style="color: var(--text-muted);">Capacidad del Vehículo:</span>
<span style="color: #fff; font-weight: 600;">Máximo 4 Pasajeros (1 cupo solicitado)</span>
</div>
<div class="summary-item">
<span style="color: var(--text-muted);">Recogida Puerta a Puerta:</span>
<span style="color: var(--accent-cyan); font-weight: 600;">Incluida en Cali</span>
</div>
<div class="summary-item">
<span style="color: var(--text-muted);">Navegación Conductor:</span>
<span style="color: #fff; font-weight: 600;">Waze Deep Link con TSP</span>
</div>
<div class="bold-gateway-badge">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="5" width="20" height="14" rx="2"></rect>
<line x1="2" y1="10" x2="22" y2="10"></line>
</svg>
<span>Pago 100% Prepagado con <strong>Bold Smart Checkout</strong> (QR Bre-B, Nequi, PSE)</span>
</div>
<!-- ALERTA DE REGLA DE NEGOCIO -->
<div style="background: rgba(6, 182, 212, 0.1); border: 1px solid rgba(6, 182, 212, 0.3); border-radius: 8px; padding: 0.85rem; font-size: 0.84rem; color: #cbd5e1; margin-bottom: 1rem;">
ℹ️ <strong>Protocolo de Notificación Oficial:</strong>
El disparo inmediato de notificación a los 4 pasajeros se ejecuta <strong>únicamente cuando el Administrador asigna el conductor</strong>, garantizando que el vehículo asignado sea el más cercano a su residencia en Cali.
</div>
</div>
<div>
<div class="summary-total">
<span style="font-weight: 600; color: #fff;">Tarifa por Pasajero:</span>
<span class="total-price">$35.000 COP</span>
</div>
<button class="btn-primary" id="btn-book-pool" style="margin-top: 1rem;">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
</svg>
Confirmar y Pagar Cupo ($35.000 COP)
</button>
</div>
</div>
</div>
<!-- Alerta tras completar cupo -->
<div id="pool-dispatch-alert" style="display: none; margin-top: 2rem; background: rgba(245, 158, 11, 0.12); border: 1px solid rgba(245, 158, 11, 0.4); border-radius: 14px; padding: 1.5rem;">
<div style="display: flex; gap: 1rem; align-items: flex-start;">
<span style="font-size: 2rem;">🚗💨</span>
<div>
<h4 style="color: #fbbf24; font-size: 1.15rem; margin-bottom: 0.4rem;">¡Vehículo con 4/4 Cupos Completados!</h4>
<p style="color: #cbd5e1; font-size: 0.92rem; margin-bottom: 0.75rem;">
El viaje ha cambiado su estado a <code>POOL_COMPLETED</code>. Se ha enviado una alerta inmediata al Administrador para seleccionar el conductor de acuerdo a la cercanía de su residencia en Cali respecto a la primera recogida.
</p>
<div style="font-size: 0.85rem; color: #94a3b8;">
📲 Una vez asignado, se disparará automáticamente el mensaje de WhatsApp a los 4 pasajeros con los datos del conductor y horario exacto de recogida.
</div>
</div>
</div>
</div>
</div>
</section>
<!-- TAB 2: FLETES Y ENCOMIENDAS -->
<section class="tab-content" id="tab-fletes">
<div class="glass-panel">
<div class="panel-header">
<h2>Cotizador Inteligente de Mensajería & Fletes</h2>
<p>Envíos seguros en el corredor Cali - Buenaventura. Tarifa exacta calculada por distancia, factor de peso/volumen y seguro obligatorio.</p>
</div>
<div class="grid-2col">
<div>
<div class="form-group">
<label class="form-label" for="package-type">Tipo de Encomienda / Paquete</label>
<select class="form-select" id="package-type">
<option value="1.0">Sobre / Documentos Urgentes (Factor 1.0)</option>
<option value="1.2" selected>Paquete Pequeño (hasta 5 kg) - Factor 1.2</option>
<option value="1.6">Paquete Mediano (hasta 15 kg) - Factor 1.6</option>
<option value="2.2">Carga Grande / Caja Pesada (hasta 30 kg) - Factor 2.2</option>
</select>
</div>
<div class="form-group">
<label class="form-label" for="declared-value">Valor Declarado de la Mercancía ($ COP)</label>
<input type="number" class="form-input" id="declared-value" placeholder="100000" value="150000">
<span style="font-size: 0.75rem; color: var(--text-dim); margin-top: 4px; display: block;">El seguro corresponde al 1% del valor declarado ante extravío o siniestro.</span>
</div>
<div class="grid-2col" style="gap: 1rem;">
<div class="form-group">
<label class="form-label" for="sender-phone">Teléfono Remitente (WhatsApp)</label>
<input type="tel" class="form-input" id="sender-phone" value="3167455877">
</div>
<div class="form-group">
<label class="form-label" for="receiver-phone">Teléfono Destinatario (WhatsApp)</label>
<input type="tel" class="form-input" id="receiver-phone" value="3101234567">
</div>
</div>
<div class="form-group">
<label class="form-label" for="package-desc">Descripción del Contenido</label>
<input type="text" class="form-input" id="package-desc" placeholder="Repuestos automotrices / Ropa / Medicamentos">
</div>
</div>
<div class="summary-box">
<div>
<h3 style="font-size: 1.25rem; color: #fff; margin-bottom: 1rem;">Fórmula Oficial de Liquidación</h3>
<div style="background: rgba(0,0,0,0.4); border-radius: 8px; padding: 0.75rem; font-family: monospace; font-size: 0.8rem; color: #38bdf8; margin-bottom: 1rem;">
Tarifa = (Base + [115 Km × $1.200]) × Factor + Seguro (1%)
</div>
<div class="summary-item">
<span style="color: var(--text-muted);">Tarifa Base Logística:</span>
<span style="color: #fff; font-weight: 600;" id="freight-base-cost">$25.000 COP</span>
</div>
<div class="summary-item">
<span style="color: var(--text-muted);">Costo por Distancia (115 km):</span>
<span style="color: #fff; font-weight: 600;" id="freight-dist-cost">$165.600 COP</span>
</div>
<div class="summary-item">
<span style="color: var(--text-muted);">Seguro de Mercancía:</span>
<span style="color: var(--accent-emerald); font-weight: 600;" id="freight-insurance-cost">$1.500 COP</span>
</div>
<div class="bold-gateway-badge">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
<span>100% Prepagado antes del despacho con Bold Smart Checkout</span>
</div>
</div>
<div>
<div class="summary-total">
<span style="font-weight: 600; color: #fff;">Flete Total Liquidado:</span>
<span class="total-price" id="freight-total-price">$197.100 COP</span>
</div>
<button class="btn-primary" style="margin-top: 1rem;">
Generar Guía y Enlace de Pago Bold
</button>
</div>
</div>
</div>
</div>
</section>
<!-- TAB 3: RADAR DE FLOTA & DESPACHO -->
<section class="tab-content" id="tab-radar">
<div class="glass-panel">
<div class="panel-header">
<h2>Radar Satelital GPS & Asignación por Residencia</h2>
<p>Visualización en tiempo real de la flota en el corredor Cali - Buenaventura y asignación inteligente de conductores basada en la distancia de su lugar de residencia a la primera recogida.</p>
</div>
<!-- Radar Visualizer -->
<div class="radar-screen" id="radar-canvas-container">
<div class="radar-grid"></div>
<div class="radar-sweep"></div>
<div class="corridor-line"></div>
<div class="corridor-point cali" title="Cali"></div>
<div class="corridor-point dagua" title="Dagua"></div>
<div class="corridor-point lobo" title="Loboguerrero"></div>
<div class="corridor-point buena" title="Buenaventura"></div>
<div style="position: absolute; bottom: 15px; left: 20px; font-size: 0.8rem; color: #64748b; font-family: monospace;">
🟢 Cali (Km 0) ── 🔵 Dagua (Km 45) ── 🔵 Loboguerrero (Km 60) ── 🟡 Buenaventura (Km 115)
</div>
<div class="radar-vehicle" style="top: 45%; left: 22%;" title="Renault Duster - Carlos Mario">🚗</div>
<div class="radar-vehicle" style="top: 48%; left: 52%;" title="Toyota Fortuner - Hernán Darío">🚙</div>
<div class="radar-vehicle" style="top: 43%; left: 78%;" title="Chevrolet Tracker - Mauricio V.">🚗</div>
</div>
<!-- Sugerencia de Conductores por Residencia -->
<div>
<h3 style="font-size: 1.25rem; color: #fff; margin-bottom: 0.5rem;">Conductores Sugeridos para el Viaje Actual</h3>
<p style="font-size: 0.88rem; color: var(--text-muted); margin-bottom: 1.25rem;">
Ordenados según la cercanía entre su domicilio registrado en Cali y la 1ª recogida del grupo de pasajeros.
</p>
<div id="suggested-drivers-list">
<!-- Renderizado dinámicamente por app.js -->
</div>
<div id="schedule-dispatch-result" style="display: none; margin-top: 1.5rem;"></div>
</div>
</div>
</section>
<!-- TAB 4: PORTAL CONDUCTOR -->
<section class="tab-content" id="tab-conductores">
<div class="glass-panel">
<div class="panel-header">
<h2>Vinculación Oficial de Conductores</h2>
<p>Únete a la flota oficial de Pacifictrans Go. Registra tu vehículo y sube tus documentos para validación administrativa.</p>
</div>
<div class="grid-2col">
<div>
<div class="form-group">
<label class="form-label" for="driver-name">Nombre y Apellidos</label>
<input type="text" class="form-input" id="driver-name" placeholder="Nombre del conductor">
</div>
<div class="form-group">
<label class="form-label" for="driver-residence">Lugar de Residencia en Cali (Barrio / Comuna)</label>
<input type="text" class="form-input" id="driver-residence" placeholder="Ej: Barrio Granada, Comuna 2, Cali">
</div>
<div class="grid-2col" style="gap: 1rem; margin-bottom: 0;">
<div class="form-group">
<label class="form-label" for="driver-plate">Placa del Vehículo</label>
<input type="text" class="form-input" id="driver-plate" placeholder="Ej: WLZ-890">
</div>
<div class="form-group">
<label class="form-label" for="driver-model">Marca y Modelo</label>
<input type="text" class="form-input" id="driver-model" placeholder="Renault Duster 2024">
</div>
</div>
</div>
<div>
<div class="form-group">
<label class="form-label">Documentación Obligatoria (Appwrite Storage)</label>
<div style="display: flex; flex-direction: column; gap: 0.75rem;">
<div style="background: rgba(15, 23, 42, 0.9); border: 1px dashed var(--border-glass); padding: 0.85rem; border-radius: 8px; display: flex; justify-content: space-between; align-items: center;">
<span style="font-size: 0.85rem; color: #cbd5e1;">📄 SOAT Vigente</span>
<button style="background: rgba(6, 182, 212, 0.15); border: 1px solid var(--accent-cyan); color: var(--accent-cyan); padding: 4px 10px; border-radius: 4px; font-size: 0.75rem; cursor: pointer;">Subir Archivo</button>
</div>
<div style="background: rgba(15, 23, 42, 0.9); border: 1px dashed var(--border-glass); padding: 0.85rem; border-radius: 8px; display: flex; justify-content: space-between; align-items: center;">
<span style="font-size: 0.85rem; color: #cbd5e1;">📄 Revisión Técnico-Mecánica</span>
<button style="background: rgba(6, 182, 212, 0.15); border: 1px solid var(--accent-cyan); color: var(--accent-cyan); padding: 4px 10px; border-radius: 4px; font-size: 0.75rem; cursor: pointer;">Subir Archivo</button>
</div>
<div style="background: rgba(15, 23, 42, 0.9); border: 1px dashed var(--border-glass); padding: 0.85rem; border-radius: 8px; display: flex; justify-content: space-between; align-items: center;">
<span style="font-size: 0.85rem; color: #cbd5e1;">📄 Tarjeta de Operación Especial</span>
<button style="background: rgba(6, 182, 212, 0.15); border: 1px solid var(--accent-cyan); color: var(--accent-cyan); padding: 4px 10px; border-radius: 4px; font-size: 0.75rem; cursor: pointer;">Subir Archivo</button>
</div>
<div style="background: rgba(15, 23, 42, 0.9); border: 1px dashed var(--border-glass); padding: 0.85rem; border-radius: 8px; display: flex; justify-content: space-between; align-items: center;">
<span style="font-size: 0.85rem; color: #cbd5e1;">📄 Licencia de Conducción C1 / C2</span>
<button style="background: rgba(6, 182, 212, 0.15); border: 1px solid var(--accent-cyan); color: var(--accent-cyan); padding: 4px 10px; border-radius: 4px; font-size: 0.75rem; cursor: pointer;">Subir Archivo</button>
</div>
</div>
</div>
<button class="btn-primary" style="margin-top: 1rem;">
Enviar Solicitud de Vinculación
</button>
</div>
</div>
</div>
</section>
<!-- SECCIÓN DESCARGA APK -->
<section class="download-card" id="descarga-apk">
<div>
<span style="color: #22c55e; font-weight: 700; text-transform: uppercase; font-size: 0.8rem; letter-spacing: 0.08em;">App Oficial Android</span>
<h3 style="font-size: 2.2rem; color: #fff; margin-top: 0.25rem; margin-bottom: 0.75rem;">Pacifictrans Go Móvil</h3>
<p style="color: var(--text-muted); max-width: 500px; font-size: 1rem;">
Instala la aplicación nativa en tu dispositivo Android. Accede a navegación por Waze en un toque, pagos instantáneos con Bold y seguimiento en vivo de tu viaje.
</p>
</div>
<a href="/PacifictransGo.apk" class="btn-apk-large" id="btn-download-apk-footer">
<svg width="28" height="28" viewBox="0 0 24 24" fill="currentColor">
<path d="M17.523 15.3414c-.5511 0-.9993-.4486-.9993-1.0001s.4482-1.0001.9993-1.0001c.552 0 1.0001.4486 1.0001 1.0001s-.4481 1.0001-1.0001 1.0001m-11.046 0c-.5511 0-.9993-.4486-.9993-1.0001s.4482-1.0001.9993-1.0001c.552 0 1.0001.4486 1.0001 1.0001s-.4481 1.0001-1.0001 1.0001m11.4045-6.02l1.997-3.4589c.1408-.2439.0573-.5552-.1866-.696-.2438-.1408-.5551-.0573-.696.1866l-2.0238 3.5054c-1.5037-.6873-3.1979-1.0772-4.9691-1.0772s-3.4654.3899-4.9691 1.0772l-2.0238-3.5054c-.1409-.2439-.4522-.3274-.696-.1866-.2439.1408-.3274.4521-.1866.696l1.997 3.4589c-3.7997 2.0623-6.375 5.9863-6.446 10.5369h24.646c-.071-4.5506-2.6463-8.4746-6.446-10.5369"/>
</svg>
Descargar PacifictransGo.apk (37.1 MB)
</a>
</section>
</div>
</main>
<!-- FOOTER -->
<footer class="footer">
<div style="max-width: 1280px; margin: 0 auto; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem;">
<div>
<strong style="color: #fff;">Pacifictrans Go</strong> — Transporte Especial Cali - Buenaventura
</div>
<div>
Atención y Soporte WhatsApp: <a href="https://wa.me/573167455877" target="_blank" style="color: var(--accent-cyan); text-decoration: none;">+57 316 745 5877</a>
</div>
<div>
© 2026 Almeira Group. Todos los derechos reservados.
</div>
</div>
</footer>
<script src="/js/app.js"></script>
</body>
</html>

View File

@ -0,0 +1,195 @@
// Pacifictrans Go - Interactive Web Platform Logic
document.addEventListener('DOMContentLoaded', () => {
initTabs();
initPassengerCalculator();
initFreightCalculator();
initRadarSimulation();
});
// TABS NAVIGATION
function initTabs() {
const tabBtns = document.querySelectorAll('.tab-btn');
const tabContents = document.querySelectorAll('.tab-content');
tabBtns.forEach(btn => {
btn.addEventListener('click', () => {
const targetId = btn.getAttribute('data-tab');
tabBtns.forEach(b => b.classList.remove('active'));
tabContents.forEach(c => c.classList.remove('active'));
btn.classList.add('active');
const targetEl = document.getElementById(targetId);
if (targetEl) targetEl.classList.add('active');
});
});
}
// PASSENGER POOLING & BOOKING LOGIC
function initPassengerCalculator() {
const originSelect = document.getElementById('pool-origin');
const addressGroup = document.getElementById('cali-address-group');
const buenaStopGroup = document.getElementById('buena-stop-group');
const bookBtn = document.getElementById('btn-book-pool');
const poolDots = document.querySelectorAll('.seat-dot');
const poolBadge = document.getElementById('pool-status-badge');
let currentSeatsFilled = 3; // Starts at 3/4 for instant visual demonstration
function updateOriginDestination() {
if (!originSelect) return;
const isCali = originSelect.value === 'CALI';
if (addressGroup) {
addressGroup.style.display = isCali ? 'block' : 'none';
}
if (buenaStopGroup) {
buenaStopGroup.style.display = isCali ? 'block' : 'none';
}
}
if (originSelect) {
originSelect.addEventListener('change', updateOriginDestination);
updateOriginDestination();
}
if (bookBtn) {
bookBtn.addEventListener('click', async (e) => {
e.preventDefault();
bookBtn.disabled = true;
bookBtn.innerHTML = `
<svg class="animate-spin" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10" stroke-opacity="0.25"></circle>
<path d="M12 2a10 10 0 0 1 10 10" stroke="currentColor"></path>
</svg> Procesando Agrupación Geoespacial...
`;
setTimeout(() => {
currentSeatsFilled = 4;
updateSeatVisuals(currentSeatsFilled);
if (poolBadge) {
poolBadge.innerHTML = `🚨 <strong>¡CUPO COMPLETO (4/4)!</strong> Viaje en estado <code>POOL_COMPLETED</code>. Notificando a la Administración para asignar conductor óptimo según lugar de residencia en Cali.`;
poolBadge.style.background = 'rgba(245, 158, 11, 0.15)';
poolBadge.style.borderColor = 'rgba(245, 158, 11, 0.4)';
poolBadge.style.color = '#fbbf24';
}
const alertBox = document.getElementById('pool-dispatch-alert');
if (alertBox) {
alertBox.style.display = 'block';
alertBox.scrollIntoView({ behavior: 'smooth' });
}
bookBtn.disabled = false;
bookBtn.innerHTML = `✅ Cupo Reservado Exitosamente ($35.000 COP)`;
}, 1200);
});
}
function updateSeatVisuals(count) {
poolDots.forEach((dot, index) => {
dot.classList.remove('active', 'pending');
if (index < count) {
dot.classList.add('active');
} else if (index === count) {
dot.classList.add('pending');
}
});
const seatCounter = document.getElementById('seat-counter-text');
if (seatCounter) seatCounter.textContent = `${count}/4 Pasajeros`;
}
updateSeatVisuals(currentSeatsFilled);
}
// FREIGHT & PARCEL CALCULATOR
function initFreightCalculator() {
const packageTypeSelect = document.getElementById('package-type');
const declaredValueInput = document.getElementById('declared-value');
const totalPriceEl = document.getElementById('freight-total-price');
const baseCostEl = document.getElementById('freight-base-cost');
const distCostEl = document.getElementById('freight-dist-cost');
const insuranceCostEl = document.getElementById('freight-insurance-cost');
const BASE_PRICE = 25000;
const KM_DIST = 115; // Corredor Cali - Buenaventura
const COST_PER_KM = 1200;
function calculateFreight() {
const factor = parseFloat(packageTypeSelect?.value || '1.0');
const declaredValue = parseFloat(declaredValueInput?.value || '0');
const distancePart = KM_DIST * COST_PER_KM; // 138,000
const subtotal = (BASE_PRICE + distancePart) * factor;
const seguro = Math.round(declaredValue * 0.01);
const total = Math.round(subtotal + seguro);
if (baseCostEl) baseCostEl.textContent = `$${BASE_PRICE.toLocaleString('es-CO')} COP`;
if (distCostEl) distCostEl.textContent = `$${Math.round(distancePart * factor).toLocaleString('es-CO')} COP`;
if (insuranceCostEl) insuranceCostEl.textContent = `$${seguro.toLocaleString('es-CO')} COP`;
if (totalPriceEl) totalPriceEl.textContent = `$${total.toLocaleString('es-CO')} COP`;
}
if (packageTypeSelect) packageTypeSelect.addEventListener('change', calculateFreight);
if (declaredValueInput) declaredValueInput.addEventListener('input', calculateFreight);
calculateFreight();
}
// RADAR FLEET SIMULATION & LIVE DATA
function initRadarSimulation() {
const radarContainer = document.getElementById('radar-canvas-container');
if (!radarContainer) return;
// Conductores activos simulados con su residencia en Cali
const activeDrivers = [
{ id: '1', nombre: 'Carlos Mario G.', vehiculo: 'Renault Duster (WLZ-890)', barrio: 'Granada (Comuna 2)', distCali: '1.2 km', lat: 3.455, lng: -76.535, estado: 'DISPONIBLE' },
{ id: '2', nombre: 'Hernán Darío P.', vehiculo: 'Toyota Fortuner (VBF-312)', barrio: 'San Antonio (Comuna 3)', distCali: '2.4 km', lat: 3.448, lng: -76.541, estado: 'ASIGNADO' },
{ id: '3', nombre: 'Mauricio V.', vehiculo: 'Chevrolet Tracker (HJK-456)', barrio: 'El Ingenio (Comuna 17)', distCali: '6.8 km', lat: 3.385, lng: -76.529, estado: 'DISPONIBLE' },
{ id: '4', nombre: 'Javier Restrepo', vehiculo: 'Nissan Kicks (RTY-789)', barrio: 'Limonar (Comuna 19)', distCali: '5.1 km', lat: 3.402, lng: -76.538, estado: 'EN_RUTA' }
];
const driverListEl = document.getElementById('suggested-drivers-list');
if (driverListEl) {
driverListEl.innerHTML = activeDrivers.map((d, index) => `
<div class="driver-card">
<div>
<strong style="color: #fff; font-size: 1.05rem;">#${index + 1} ${d.nombre}</strong>
<div style="font-size: 0.85rem; color: #94a3b8;">${d.vehiculo}</div>
<div style="font-size: 0.82rem; color: #06b6d4; margin-top: 3px;">
🏠 Residencia en Cali: <strong>${d.barrio}</strong>
</div>
<span class="driver-tag">Distancia al 1er Pasajero: ${d.distCali}</span>
</div>
<div style="text-align: right;">
<span style="display: inline-block; padding: 4px 10px; border-radius: 6px; font-size: 0.75rem; font-weight: 700; background: ${d.estado === 'DISPONIBLE' ? 'rgba(16, 185, 129, 0.2)' : 'rgba(245, 158, 11, 0.2)'}; color: ${d.estado === 'DISPONIBLE' ? '#10b981' : '#f59e0b'};">
${d.estado}
</span>
<button onclick="asignarConductorDemo('${d.nombre}', '${d.barrio}')" style="margin-top: 8px; display: block; background: #0f172a; border: 1px solid rgba(6, 182, 212, 0.4); color: #06b6d4; font-size: 0.8rem; padding: 5px 12px; border-radius: 6px; cursor: pointer;">
Asignar y Programar
</button>
</div>
</div>
`).join('');
}
}
// Ventana de asignación y disparo de notificación WhatsApp
window.asignarConductorDemo = function(conductor, barrio) {
const resultDiv = document.getElementById('schedule-dispatch-result');
if (resultDiv) {
resultDiv.style.display = 'block';
resultDiv.innerHTML = `
<div style="background: rgba(16, 185, 129, 0.15); border: 1px solid rgba(16, 185, 129, 0.4); border-radius: 12px; padding: 1.25rem; color: #10b981;">
<h4 style="margin-bottom: 0.5rem; font-size: 1.1rem; color: #fff;">✅ Conductor Asignado por Residencia: <strong>${conductor}</strong> (${barrio})</h4>
<p style="color: #cbd5e1; font-size: 0.92rem; margin-bottom: 0.75rem;">
🚀 <strong>Disparo Inmediato de Notificación WhatsApp Ejecutado:</strong>
Los 4 pasajeros han recibido su confirmación oficial con el conductor asignado, vehículo, hora estimada escalonada de recogida y enlace de Waze / mapa en vivo.
</p>
<div style="background: rgba(0,0,0,0.4); padding: 0.75rem; border-radius: 8px; font-family: monospace; font-size: 0.85rem; color: #6ee7b7;">
📲 [Evolution API WhatsApp Status: 201 Created] Enviado a los 4 Pasajeros + Conductor.
</div>
</div>
`;
resultDiv.scrollIntoView({ behavior: 'smooth' });
}
};

View File

@ -1,4 +1,6 @@
import http from 'http';
import fs from 'fs';
import path from 'path';
import { connectDatabase } from './config/database';
import { poolingService } from './services/pooling.service';
import { messagingFleteService } from './services/messaging-flete.service';
@ -11,6 +13,21 @@ import { tripSchedulingService } from './services/trip-scheduling.service';
import { Trip } from './models/Trip';
const PORT = process.env.PORT || 4000;
const PUBLIC_DIR = path.resolve(__dirname, '../public');
const MIME_TYPES: Record<string, string> = {
'.html': 'text/html; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.js': 'application/javascript; charset=utf-8',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.apk': 'application/vnd.android.package-archive',
'.woff2': 'font/woff2'
};
// Servidor HTTP nativo Node.js sin dependencias pesadas extras
const server = http.createServer(async (req, res) => {
@ -196,8 +213,54 @@ const server = http.createServer(async (req, res) => {
return sendJson(200, updateResult);
}
// 14. Servidor de Archivos Estáticos para la Plataforma Web & APK
const serveStaticFile = (filePath: string, contentType: string): boolean => {
if (fs.existsSync(filePath)) {
const stat = fs.statSync(filePath);
if (stat.isFile()) {
res.writeHead(200, {
'Content-Type': contentType,
'Content-Length': stat.size,
'Cache-Control': contentType.includes('apk') ? 'no-cache' : 'public, max-age=3600'
});
fs.createReadStream(filePath).pipe(res);
return true;
}
}
return false;
};
// Descarga directa del APK Oficial Android
if (pathname === '/PacifictransGo.apk' || pathname === '/download/apk') {
const localApk = path.join(PUBLIC_DIR, 'PacifictransGo.apk');
const fallbackApk = path.resolve(__dirname, '../../../pacifictrans-app/public/PacifictransGo.apk');
const vpsApk = '/home/carlos/apps/dripzone-app/public/PacifictransGo.apk';
const targetApk = fs.existsSync(localApk) ? localApk : (fs.existsSync(fallbackApk) ? fallbackApk : vpsApk);
if (serveStaticFile(targetApk, MIME_TYPES['.apk'])) return;
}
// Archivo estático directo (HTML, CSS, JS, Imágenes)
let relativePath = pathname === '/' ? 'index.html' : pathname.replace(/^\//, '');
let targetPath = path.join(PUBLIC_DIR, relativePath);
const ext = path.extname(targetPath).toLowerCase();
const mime = MIME_TYPES[ext] || 'application/octet-stream';
if (serveStaticFile(targetPath, mime)) {
return;
}
// Fallback SPA a index.html para rutas sin extensión
if (!ext) {
const indexPath = path.join(PUBLIC_DIR, 'index.html');
if (serveStaticFile(indexPath, MIME_TYPES['.html'])) {
return;
}
}
// Ruta no encontrada
sendJson(404, { error: 'Endpoint no encontrado' });
sendJson(404, { error: 'Recurso o Endpoint no encontrado' });
} catch (error: any) {
console.error('❌ [API Error]:', error);
sendJson(500, { error: error.message || 'Error interno del servidor' });

View File

@ -0,0 +1,78 @@
# Guía de Despliegue en Coolify desde Servidor Git: Pacifictrans Go
Esta guía detalla los pasos para desplegar la plataforma web y API de **Pacifictrans Go** en **Coolify** utilizando el repositorio Git propio de Gitea (`https://gitea.almeira.com.co/carlos/almeira-web.git`), con subdominio oficial en `almeira.com.co` y certificado SSL automático gestionado por Traefik.
---
## 1. Parámetros del Recurso en Coolify
| Parámetro | Valor Oficial |
| :--- | :--- |
| **Tipo de Recurso** | Application |
| **Origen (Git Source)** | Gitea / Git Repository |
| **URL del Repositorio** | `https://gitea.almeira.com.co/carlos/almeira-web.git` |
| **Rama (Branch)** | `main` |
| **Directorio Base (Base Directory)** | `/transporte-cali-buenaventura/backend` |
| **Build Pack** | `Dockerfile` |
| **Puerto Expuesto** | `4000` |
| **FQDN / Dominios** | `https://pacifictrans.almeira.com.co` (o alias `https://transporte.almeira.com.co`) |
---
## 2. Configuración Paso a Paso en el Panel de Coolify
1. **Crear la Aplicación**:
- En el dashboard de Coolify, entrar a tu proyecto y seleccionar **+ New Resource**.
- Seleccionar **Application** y luego **Public Repository** o conectar tu cuenta de **Gitea**.
- Ingresar la URL del repositorio: `https://gitea.almeira.com.co/carlos/almeira-web.git`.
- Indicar la rama `main`.
2. **Configurar el Directorio Base y Build Pack**:
- En **Base Directory**, escribir: `/transporte-cali-buenaventura/backend`.
- En **Build Pack**, seleccionar: `Dockerfile`. (Coolify detectará automáticamente el archivo [Dockerfile](file:///c:/Users/Carlos/dev/almeira-web/transporte-cali-buenaventura/backend/Dockerfile) multi-stage optimizado).
3. **Asignar el Dominio y Puerto**:
- En el campo **Domains**, ingresar: `https://pacifictrans.almeira.com.co`.
- En el campo **Ports Exposes**, definir: `4000`.
- Traefik generará y renovará automáticamente el certificado Let's Encrypt SSL.
4. **Variables de Entorno (Environment Variables)**:
Pegar las siguientes variables en la pestaña **Environment Variables** de la aplicación en Coolify:
```env
PORT=4000
NODE_ENV=production
# Conexión a MongoDB (Red interna de Coolify)
MONGODB_URI=mongodb://admin:AlmeiraTransporte2026Secure!@coolify-mongodb-cali:27017/transporte_cali_buenaventura?authSource=admin
# Appwrite Cloud Propio
APPWRITE_ENDPOINT=https://appwrite.almeira.com.co/v1
APPWRITE_PROJECT_ID=69b84df5003bd8957c5c
APPWRITE_DATABASE_ID=sneakers_multistore_db
APPWRITE_API_KEY=standard_cce3cdb866ea6433888d40cedd24dc45987372308fdc3dd0697de08648c0e5c7e04b02c3bcee271403fd656b00a5820f04c08b51631284a6e8a3582d8aed2aa33234f3c29cc777e9b162eb54810b789251b0d006a9a3b081ebc2da53b9baa94614b124b3508ad849fd9a4187cc6094f506d4b57f409b69d9562a74df594f3ec3
# WhatsApp Evolution API
EVOLUTION_API_URL=https://evolution.almeira.com.co
EVOLUTION_API_KEY=EE49029EEB85-4568-8F81-C9FDFC6E2943
EVOLUTION_INSTANCE=AutomataAlmeira
# Bold Smart Checkout (Producción)
BOLD_IDENTITY_KEY=k_live_a34f8902bc45de
BOLD_SECRET_KEY=s_live_902bc45de890a3
BOLD_INTEGRITY_KEY=i_live_bc45de890a34f8
```
5. **Despliegue y Webhook Automático (CI/CD)**:
- Hacer clic en **Deploy** para iniciar el primer despliegue.
- En los ajustes de la aplicación en Coolify, copiar la URL del **Webhook de Despliegue**.
- En Gitea (`https://gitea.almeira.com.co/carlos/almeira-web/settings/hooks`), agregar el Webhook para el evento `Push`. De esta forma, cada commit enviado al servidor Git desplegará automáticamente la versión más reciente.
---
## 3. URLs Disponibles una vez Desplegado
- **Página Web Principal**: `https://pacifictrans.almeira.com.co/`
- **Descarga Directa del APK Android**: `https://pacifictrans.almeira.com.co/PacifictransGo.apk`
- **API REST & Telemetría**: `https://pacifictrans.almeira.com.co/api/health`
- **Webhook Oficial de Pagos Bold**: `https://pacifictrans.almeira.com.co/api/payments/bold/webhook`

View File

@ -547,7 +547,7 @@ export interface ZoneDemandStat {
})
export class AdminLiveDashboardComponent implements OnInit, OnDestroy {
private refreshTimer: any;
private apiUrl = 'https://api-transporte.almeira.com.co/api';
private apiUrl = 'https://pacifictrans.almeira.com.co/api';
drivers: DriverLiveDTO[] = [];
selectedDriver: DriverLiveDTO | null = null;

View File

@ -37,7 +37,7 @@ export interface PackagePayload {
providedIn: 'root',
})
export class TripService {
private apiUrl: string = 'https://api-transporte.almeira.com.co/api';
private apiUrl: string = 'https://pacifictrans.almeira.com.co/api';
constructor() {}