Help
RSS
API
Feed
Maltego
Contact
Domain > plomeros.com.ar
×
More information on this domain is in
AlienVault OTX
Is this malicious?
Yes
No
DNS Resolutions
Date
IP Address
2016-02-12
190.228.48.175
(
ClassC
)
2026-03-07
199.36.158.100
(
ClassC
)
Port 443
HTTP/1.1 200 OKConnection: keep-aliveContent-Length: 104163Cache-Control: max-age3600Content-Type: text/html; charsetutf-8Etag: 52361e9565b0bc2fa04f4a426ae26fb97cb256a0dd78c3d1ce153d8b8a842317Last-Modified: Wed, 04 Mar 2026 17:49:36 GMTStrict-Transport-Security: max-age31556926Accept-Ranges: bytesDate: Sat, 07 Mar 2026 23:02:13 GMTX-Served-By: cache-bfi-krnt7300037-BFIX-Cache: MISSX-Cache-Hits: 0X-Timer: S1772924533.989724,VS0,VE382Vary: x-fh-requested-host, accept-encodingalt-svc: h3:443;ma86400,h3-29:443;ma86400,h3-27:443;ma86400 !DOCTYPE html>html langes>head> meta charsetUTF-8> meta nameviewport contentwidthdevice-width, initial-scale1.0> title>Plomeros.com.ar — Encontrá tu profesional en Buenos Aires/title> meta namedescription contentDirectorio de plomeros, electricistas, gasistas y más en Buenos Aires y CABA. Publicá tu problema, recibí cotizaciones, leé reviews.> !-- Google Analytics GA4 --> script async srchttps://www.googletagmanager.com/gtag/js?idG-8JBHGK4X0J>/script> script> window.dataLayer window.dataLayer || ; function gtag(){dataLayer.push(arguments);} gtag(js, new Date()); gtag(config, G-8JBHGK4X0J); /script> !-- Favicon --> link relicon typeimage/svg+xml hrefdata:image/svg+xml,%3Csvg xmlnshttp://www.w3.org/2000/svg viewBox0 0 100 100%3E%3Ccircle cx50 cy50 r45 fill%232563eb/%3E%3Ctext x50 y60 text-anchormiddle font-size40 fillwhite%3E🔧%3C/text%3E%3C/svg%3E> link relapple-touch-icon hrefdata:image/svg+xml,%3Csvg xmlnshttp://www.w3.org/2000/svg viewBox0 0 100 100%3E%3Ccircle cx50 cy50 r45 fill%232563eb/%3E%3Ctext x50 y60 text-anchormiddle font-size40 fillwhite%3E🔧%3C/text%3E%3C/svg%3E> !-- Open Graph tags --> meta propertyog:title contentPlomeros.com.ar — Encontrá tu profesional en Buenos Aires> meta propertyog:description contentDirectorio de plomeros, electricistas, gasistas y más en Buenos Aires y CABA. Publicá tu problema, recibí cotizaciones, leé reviews.> meta propertyog:image contenthttps://plomeros.com.ar/og-image.jpg> meta propertyog:url contenthttps://plomeros.com.ar> meta propertyog:type contentwebsite> !-- Google Search Console --> meta namegoogle-site-verification contentGOOGLE_VERIFICATION_CODE_PLACEHOLDER> !-- Schema.org markup --> script typeapplication/ld+json> { @context: https://schema.org, @type: WebSite, name: Plomeros.com.ar, description: Directorio de profesionales en Buenos Aires, url: https://plomeros.com.ar, potentialAction: { @type: SearchAction, target: https://plomeros.com.ar?q{search_term_string}, query-input: required namesearch_term_string } } /script> script typeapplication/ld+json> { @context: https://schema.org, @type: LocalBusiness, name: Plomeros.com.ar, description: Directorio de profesionales de plomería, electricidad, gas y más en Buenos Aires, address: { @type: PostalAddress, addressLocality: Buenos Aires, addressRegion: CABA, addressCountry: AR }, url: https://plomeros.com.ar, telephone: +54-11-XXXX-XXXX, openingHours: Mo-Su 00:00-23:59 } /script> link relstylesheet hrefhttps://unpkg.com/leaflet@1.9.4/dist/leaflet.css /> script srchttps://unpkg.com/leaflet@1.9.4/dist/leaflet.js>/script> link relstylesheet hrefhttps://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css> link relstylesheet hrefhttps://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css> script srchttps://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js>/script> link hrefhttps://fonts.googleapis.com/css2?familyInter:wght@400;500;600;700;800&displayswap relstylesheet> style> :root { --primary: #2563eb; --primary-dark: #1d4ed8; --primary-light: #dbeafe; --accent: #f59e0b; --bg: #f8fafc; --card: #ffffff; --text: #1e293b; --text-light: #64748b; --border: #e2e8f0; --success: #10b981; --danger: #ef4444; --radius: 12px; --whatsapp: #25d366; --whatsapp-dark: #1fa855; --shadow-sm: 0 1px 3px rgba(0,0,0,0.08); --shadow-md: 0 4px 16px rgba(0,0,0,0.1); --shadow-lg: 0 8px 30px rgba(0,0,0,0.15); } * { margin:0; padding:0; box-sizing:border-box; } body { font-family:Inter,-apple-system,sans-serif; background:var(--bg); color:var(--text); line-height:1.6; -webkit-font-smoothing:antialiased; } /* ── NAV ── */ nav.topnav { background:white; border-bottom:1px solid var(--border); position:sticky; top:0; z-index:1000; } .nav-inner { max-width:1400px; margin:0 auto; padding:12px 24px; display:flex; align-items:center; justify-content:space-between; } .logo { font-size:1.5rem; font-weight:800; letter-spacing:-0.5px; color:var(--text); text-decoration:none; } .logo span { color:var(--primary); } .nav-tabs { display:flex; gap:4px; } .nav-tab { padding:10px 20px; border-radius:10px; cursor:pointer; font-weight:600; font-size:0.9rem; color:var(--text-light); border:none; background:none; font-family:inherit; transition:all 0.2s; } .nav-tab:hover { background:#f1f5f9; color:var(--text); } .nav-tab.active { background:var(--primary); color:white; } .nav-auth { display:flex; gap:8px; align-items:center; } .btn { padding:10px 20px; border-radius:10px; font-weight:600; font-size:0.9rem; cursor:pointer; border:none; font-family:inherit; transition:all 0.15s; } .btn-outline { background:white; border:1px solid var(--border); color:var(--text); } .btn-outline:hover { border-color:var(--primary); color:var(--primary); } .btn-primary { background:var(--primary); color:white; } .btn-primary:hover { background:var(--primary-dark); } .btn-success { background:var(--success); color:white; } .btn-accent { background:var(--accent); color:white; } .btn-danger { background:var(--danger); color:white; } .btn-sm { padding:6px 14px; font-size:0.82rem; } .btn-lg { padding:14px 28px; font-size:1rem; } .btn:disabled { opacity:0.5; cursor:not-allowed; } /* ── USER DROPDOWN ── */ .user-dropdown { position:relative; } .user-btn { display:flex; align-items:center; gap:8px; cursor:pointer; padding:8px 14px; border-radius:10px; background:white; border:1px solid var(--border); font-family:inherit; font-size:0.9rem; font-weight:600; color:var(--text); } .user-btn:hover { border-color:var(--primary); } .user-menu { position:absolute; top:100%; right:0; margin-top:6px; background:white; border:1px solid var(--border); border-radius:10px; box-shadow:0 8px 24px rgba(0,0,0,0.12); min-width:180px; display:none; z-index:1001; overflow:hidden; } .user-menu.show { display:block; } .user-menu a { display:block; padding:12px 16px; color:var(--text); text-decoration:none; font-size:0.9rem; font-weight:500; transition:background 0.15s; } .user-menu a:hover { background:#f1f5f9; } /* ── PAGES ── */ .page { display:none; } .page.active { display:block; } /* ══════════ PAGE: BUSCAR ══════════ */ .hero-search { background:linear-gradient(135deg,#1e40af 0%,var(--primary) 40%,#7c3aed 100%); color:white; padding:48px 24px 56px; text-align:center; position:relative; overflow:hidden; } .hero-search::before { content:; position:absolute; inset:0; background:radial-gradient(ellipse at 30% 80%, rgba(255,255,255,0.08) 0%, transparent 60%); pointer-events:none; } .hero-search h1 { font-size:2.2rem; font-weight:800; margin-bottom:6px; letter-spacing:-0.5px; line-height:1.2; position:relative; } .hero-search .hero-subtitle { opacity:0.92; margin-bottom:10px; font-size:1.05rem; position:relative; } .hero-search .hero-trust-line { display:inline-flex; align-items:center; gap:8px; background:rgba(255,255,255,0.12); border:1px solid rgba(255,255,255,0.18); padding:6px 16px; border-radius:50px; font-size:0.88rem; font-weight:500; margin-bottom:22px; position:relative; } .search-bar { display:flex; gap:6px; max-width:720px; margin:0 auto; background:white; border-radius:16px; padding:6px; box-shadow:0 8px 30px rgba(0,0,0,0.18), 0 0 0 1px rgba(255,255,255,0.1); position:relative; } .search-bar select, .search-bar input { border:none; padding:14px 16px; font-size:1rem; font-family:inherit; background:transparent; outline:none; color:var(--text); } .search-bar select { border-right:2px solid var(--border); min-width:170px; cursor:pointer; } .search-bar input { flex:1; min-width:0; } .search-bar button { padding:14px 28px; border-radius:12px; font-size:1rem; } .cat-row { display:flex; gap:8px; justify-content:center; flex-wrap:wrap; padding:18px 24px 0; max-width:800px; margin:0 auto; } .cat-chip { background:rgba(255,255,255,0.15); color:white; border:1px solid rgba(255,255,255,0.25); padding:7px 16px; border-radius:50px; font-size:0.85rem; font-weight:500; cursor:pointer; transition:all 0.2s; text-decoration:none; } .cat-chip:hover, .cat-chip.active { background:white; color:var(--primary); } .emergency-chip { background:rgba(255,69,58,0.15) !important; color:#ff453a !important; border-color:rgba(255,69,58,0.25) !important; } .emergency-chip:hover, .emergency-chip.active { background:#ff453a !important; color:white !important; } .stats-row { display:flex; justify-content:center; gap:40px; padding:20px; background:rgba(0,0,0,0.12); margin-top:28px; position:relative; } .stat-n { font-size:1.5rem; font-weight:800; } .stat-l { font-size:0.8rem; opacity:0.85; text-transform:uppercase; letter-spacing:0.3px; } /* results grid */ .results-wrap { max-width:1400px; margin:0 auto; padding:24px; display:grid; grid-template-columns:1.6fr 1fr; gap:24px; min-height:500px; } #map { height:600px; border-radius:var(--radius); box-shadow:0 2px 12px rgba(0,0,0,0.08); position:sticky; top:80px; } .results-head { display:flex; justify-content:space-between; align-items:center; margin-bottom:14px; } .results-head h2 { font-size:1.15rem; font-weight:700; } .sort-sel { padding:8px 12px; border:1px solid var(--border); border-radius:8px; font-family:inherit; font-size:0.88rem; } /* pro card */ .pro-card { background:var(--card); border:1px solid var(--border); border-radius:var(--radius); padding:20px; margin-bottom:12px; cursor:pointer; transition:all 0.2s; position:relative; } .pro-card:hover { box-shadow:var(--shadow-md); border-color:var(--primary); transform:translateY(-1px); } .pro-card.verified-card { border-left:3px solid var(--primary); } .pro-top { display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:8px; } .pro-name { font-size:1.08rem; font-weight:700; line-height:1.3; } .pro-badge { padding:4px 10px; border-radius:6px; font-size:0.72rem; font-weight:600; letter-spacing:0.2px; } .badge-rec { background:#d1fae5; color:#065f46; } .badge-verified { background:var(--primary-light); color:var(--primary-dark); } .pro-zone { color:var(--text-light); font-size:0.84rem; margin-top:2px; } .pro-desc-preview { color:var(--text-light); font-size:0.85rem; margin:6px 0; line-height:1.4; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; } .pro-tags { display:flex; gap:5px; flex-wrap:wrap; margin:8px 0; } .tag { background:#f1f5f9; color:var(--text-light); padding:4px 10px; border-radius:6px; font-size:0.78rem; font-weight:500; } .pro-meta { display:flex; gap:14px; align-items:center; color:var(--text-light); font-size:0.84rem; margin-top:8px; } .pro-rating-stars { color:var(--accent); letter-spacing:1px; } .pro-rating-num { font-weight:700; color:var(--text); } .pro-btns { margin-top:12px; display:flex; flex-direction:column; gap:8px; } .btn-wa-primary { display:flex; align-items:center; justify-content:center; gap:8px; padding:13px 20px; border-radius:12px; background:var(--whatsapp); color:white; font-size:1rem; font-weight:700; text-decoration:none; min-height:50px; transition:all 0.2s; border:none; } .btn-wa-primary:hover { background:var(--whatsapp-dark); transform:translateY(-1px); box-shadow:0 4px 14px rgba(37,211,102,0.35); text-decoration:none; } .pro-btns-secondary { display:flex; gap:6px; flex-wrap:wrap; } .btn-secondary { display:inline-flex; align-items:center; gap:4px; padding:8px 14px; border-radius:8px; background:#f1f5f9; color:var(--text); font-size:0.82rem; font-weight:500; text-decoration:none; border:1px solid var(--border); cursor:pointer; font-family:inherit; min-height:36px; transition:all 0.15s; } .btn-secondary:hover { background:var(--primary); color:white; border-color:var(--primary); text-decoration:none; } .pro-name-link { color:inherit; text-decoration:none; } .pro-name-link:hover .pro-name { color:var(--primary); } /* Skeleton loading */ .skeleton-card { background:var(--card); border:1px solid var(--border); border-radius:var(--radius); padding:20px; margin-bottom:12px; } .skeleton-line { height:14px; background:linear-gradient(90deg,#e2e8f0 25%,#f1f5f9 50%,#e2e8f0 75%); background-size:200% 100%; animation:shimmer 1.5s infinite; border-radius:6px; margin-bottom:10px; } .skeleton-line.short { width:40%; } .skeleton-line.medium { width:65%; } .skeleton-line.long { width:90%; } .skeleton-line.btn { height:50px; border-radius:12px; margin-top:8px; } @keyframes shimmer { 0%{background-position:200% 0} 100%{background-position:-200% 0} } /* Emergency badge */ .emergency-badge { background:#ff453a; color:white; padding:3px 8px; border-radius:4px; font-size:0.7rem; font-weight:600; margin-left:6px; } /* Trust banner */ .trust-banner { background:linear-gradient(135deg,#f0f9ff 0%,#e0f2fe 100%); border-top:1px solid var(--border); border-bottom:1px solid var(--border); padding:18px 24px; text-align:center; } .trust-content { max-width:900px; margin:0 auto; } .trust-main { font-size:1.05rem; font-weight:700; color:var(--text); } .trust-points { display:flex; justify-content:center; gap:28px; margin-top:8px; font-size:0.88rem; color:var(--text-light); font-weight:500; } .trust-point { display:flex; align-items:center; gap:6px; } /* Mobile sticky CTA */ .mobile-sticky-cta { position:fixed; bottom:0; left:0; right:0; z-index:1002; background:linear-gradient(135deg,var(--primary) 0%,#7c3aed 100%); padding:12px 16px; display:none; box-shadow:0 -4px 20px rgba(0,0,0,0.15); transform:translateY(100%); transition:transform 0.3s ease; } .mobile-sticky-cta.show { transform:translateY(0); } .mobile-cta-content { display:flex; align-items:center; justify-content:space-between; max-width:400px; margin:0 auto; } .mobile-cta-text { color:white; } .mobile-cta-text strong { font-size:1rem; display:block; line-height:1.2; } .mobile-cta-text span { font-size:0.85rem; opacity:0.9; } .mobile-cta-btn { display:flex; align-items:center; gap:6px; padding:10px 18px; background:rgba(255,255,255,0.2); color:white; border-radius:8px; text-decoration:none; font-weight:600; font-size:0.9rem; border:1px solid rgba(255,255,255,0.3); min-height:44px; } .mobile-cta-btn:hover { background:rgba(255,255,255,0.3); } /* Load more button */ .load-more-container { text-align:center; padding:24px 20px; } .load-more-container .btn { min-width:200px; } /* ══════════ PAGE: PUBLICAR PROBLEMA ══════════ */ .post-page { max-width:700px; margin:0 auto; padding:32px 24px; } .post-page h1 { font-size:1.6rem; font-weight:800; margin-bottom:6px; } .post-page > p { color:var(--text-light); margin-bottom:28px; } .form-card { background:white; border-radius:var(--radius); padding:28px; box-shadow:0 2px 12px rgba(0,0,0,0.06); } .field { margin-bottom:18px; } .field label { display:block; font-weight:600; font-size:0.9rem; margin-bottom:6px; } .field input, .field select, .field textarea { width:100%; padding:12px 14px; border:1px solid var(--border); border-radius:8px; font-size:0.95rem; font-family:inherit; outline:none; transition:border 0.2s; } .field input:focus, .field select:focus, .field textarea:focus { border-color:var(--primary); } .field textarea { resize:vertical; min-height:100px; } .field .hint { font-size:0.8rem; color:var(--text-light); margin-top:4px; } .field .error { font-size:0.8rem; color:var(--danger); margin-top:4px; display:none; } .photo-upload { border:2px dashed var(--border); border-radius:var(--radius); padding:30px; text-align:center; cursor:pointer; transition:all 0.2s; position:relative; } .photo-upload:hover { border-color:var(--primary); background:var(--primary-light); } .photo-upload input { position:absolute; inset:0; opacity:0; cursor:pointer; } .photo-upload .icon { font-size:2rem; margin-bottom:6px; } .photo-preview { display:flex; gap:8px; flex-wrap:wrap; margin-top:10px; } .photo-preview img { width:80px; height:80px; object-fit:cover; border-radius:8px; border:1px solid var(--border); } .budget-row { display:flex; gap:12px; align-items:center; } .budget-row input { max-width:180px; } .check-grid { display:grid; grid-template-columns:1fr 1fr; gap:8px; margin-top:4px; } .check-grid label { font-weight:400; font-size:0.9rem; cursor:pointer; } /* ══════════ PAGE: MIS PEDIDOS / TRABAJOS ══════════ */ .jobs-page { max-width:900px; margin:0 auto; padding:32px 24px; } .jobs-page h1 { font-size:1.6rem; font-weight:800; margin-bottom:20px; } .job-card { background:white; border:1px solid var(--border); border-radius:var(--radius); padding:20px; margin-bottom:14px; } .job-header { display:flex; justify-content:space-between; align-items:flex-start; } .job-title { font-weight:700; font-size:1.05rem; } .job-status { padding:4px 10px; border-radius:6px; font-size:0.78rem; font-weight:600; } .status-open { background:#fef3c7; color:#92400e; } .status-quoted { background:#dbeafe; color:#1e40af; } .status-done { background:#d1fae5; color:#065f46; } .job-desc { color:var(--text-light); font-size:0.9rem; margin:8px 0; } .job-photos { display:flex; gap:6px; margin:8px 0; } .job-photos img { width:60px; height:60px; object-fit:cover; border-radius:6px; } .job-meta { font-size:0.82rem; color:var(--text-light); } /* ══════════ PAGE: REGISTRO ══════════ */ .register-page { max-width:600px; margin:0 auto; padding:32px 24px; } .register-page h1 { font-size:1.6rem; font-weight:800; margin-bottom:6px; } /* ══════════ PAGE: DASHBOARD ══════════ */ .dashboard-page { max-width:900px; margin:0 auto; padding:32px 24px; } .dashboard-page h1 { font-size:1.6rem; font-weight:800; margin-bottom:20px; } .dash-stats { display:grid; grid-template-columns:repeat(3,1fr); gap:16px; margin-bottom:24px; } .dash-stat { background:white; border:1px solid var(--border); border-radius:var(--radius); padding:20px; text-align:center; } .dash-stat .val { font-size:1.8rem; font-weight:800; color:var(--primary); } .dash-stat .lbl { font-size:0.85rem; color:var(--text-light); } .dash-section { background:white; border:1px solid var(--border); border-radius:var(--radius); padding:24px; margin-bottom:20px; } .dash-section h3 { font-size:1.1rem; font-weight:700; margin-bottom:16px; } .review-item { border-bottom:1px solid var(--border); padding:14px 0; } .review-item:last-child { border-bottom:none; } .review-stars { color:var(--accent); font-size:1.1rem; } .review-author { font-weight:600; font-size:0.9rem; } .review-text { color:var(--text-light); font-size:0.9rem; margin-top:4px; } .review-date { font-size:0.78rem; color:var(--text-light); margin-top:4px; } /* ══════════ MODALS ══════════ */ .modal-overlay { position:fixed; inset:0; background:rgba(0,0,0,0.5); display:none; align-items:center; justify-content:center; z-index:2000; padding:20px; } .modal-overlay.show { display:flex; } .modal { background:white; border-radius:var(--radius); padding:28px; max-width:500px; width:100%; max-height:90vh; overflow-y:auto; box-shadow:0 20px 60px rgba(0,0,0,0.2); } .modal h2 { font-size:1.3rem; font-weight:700; margin-bottom:18px; } .modal-close { float:right; background:none; border:none; font-size:1.5rem; cursor:pointer; color:var(--text-light); } .star-input { display:flex; gap:4px; font-size:2rem; cursor:pointer; margin:10px 0; } .star-input span { color:#e2e8f0; transition:color 0.15s; } .star-input span.active { color:var(--accent); } .star-input span:hover, .star-input span:hover ~ span { color:var(--accent); } /* ── ALERT ── */ .alert { position:fixed; top:80px; left:50%; transform:translateX(-50%); padding:14px 24px; border-radius:10px; font-weight:600; font-size:0.9rem; box-shadow:0 8px 24px rgba(0,0,0,0.15); z-index:3000; transition:opacity 0.3s, transform 0.3s; opacity:0; transform:translateX(-50%) translateY(-20px); } .alert.show { opacity:1; transform:translateX(-50%) translateY(0); } .alert-success { background:#d1fae5; color:#065f46; } .alert-error { background:#fee2e2; color:#991b1b; } .alert-info { background:var(--primary-light); color:var(--primary-dark); } /* ── FOOTER ── */ footer { background:var(--text); color:rgba(255,255,255,0.7); padding:40px 24px; text-align:center; margin-top:50px; } footer a { color:var(--accent); text-decoration:none; transition:color 0.15s; } footer a:hover { color:#fbbf24; } /* ── RESPONSIVE ── */ @media (max-width:900px) { .results-wrap { grid-template-columns:1fr; } #map { height:300px; position:static; } .hero-search h1 { font-size:1.6rem; } .search-bar { flex-direction:column; } .search-bar select { border-right:none; border-bottom:2px solid var(--border); } .budget-row { flex-direction:column; } .dash-stats { grid-template-columns:1fr; } .trust-points { flex-direction:column; gap:8px; align-items:center; } } @media (max-width:768px) { .mobile-sticky-cta { display:block; } body { padding-bottom:70px; } /* Hamburger nav para mobile */ .nav-tabs { gap:0; overflow-x:auto; -webkit-overflow-scrolling:touch; scrollbar-width:none; max-width:50vw; } .nav-tabs::-webkit-scrollbar { display:none; } .nav-tab { padding:8px 10px; font-size:0.8rem; white-space:nowrap; } .nav-auth .btn-outline { display:none; } .btn { min-height:48px; padding:12px 16px; } .cat-chip { padding:10px 16px; min-height:48px; display:flex; align-items:center; } .pro-select { padding:10px 16px; min-height:48px; } .pro-card { margin-bottom:14px; padding:16px; } .btn-wa-primary { font-size:1rem; min-height:52px; } .pro-btns-secondary { gap:4px; } .btn-secondary { flex:1; min-width:0; justify-content:center; padding:10px 10px; font-size:0.8rem; min-height:44px; } .hero-search h1 { font-size:1.4rem; } .hero-search .hero-subtitle { font-size:0.95rem; } .hero-search { padding:32px 16px 44px; } .trust-main { font-size:0.92rem; } .trust-points { font-size:0.82rem; } .stats-row { gap:24px; } .stat-n { font-size:1.2rem; } /* Ocultar mapa en mobile para priorizar listado */ #map { height:200px; border-radius:8px; } } @media (max-width:480px) { .nav-inner { padding:8px 12px; } .hero-search { padding:24px 12px 40px; } .results-wrap { padding:12px; gap:12px; } .cat-row { padding:12px 12px 0; gap:6px; } .search-bar { padding:3px; } .pro-btns-secondary { flex-wrap:wrap; } .btn-secondary { flex:1 1 calc(50% - 4px); } .logo { font-size:1.2rem; } .hero-search h1 { font-size:1.25rem; } } .empty { text-align:center; padding:50px 20px; color:var(--text-light); } .empty .e-icon { font-size:2.5rem; margin-bottom:10px; } /* ── PRO SELECT CHECKBOX ── */ .pro-select { display:inline-flex; align-items:center; gap:6px; cursor:pointer; padding:6px 14px; border-radius:8px; font-size:0.85rem; font-weight:500; background:#f1f5f9; color:var(--text-light); border:1px solid var(--border); transition:all 0.2s; user-select:none; } .pro-select:hover { border-color:var(--primary); color:var(--primary); } .pro-select inputtypecheckbox { display:none; } .pro-select.checked { background:var(--primary-light); color:var(--primary-dark); border-color:var(--primary); font-weight:600; } .pro-select .check-icon::before { content:☐; font-size:1.1rem; } .pro-select.checked .check-icon::before { content:☑; } /* ── QUOTE BAR ── */ #quoteBar { position:fixed; bottom:0; left:0; right:0; background:linear-gradient(135deg,var(--primary) 0%,#7c3aed 100%); color:white; padding:16px 24px; display:flex; align-items:center; justify-content:space-between; z-index:1001; box-shadow:0 -4px 20px rgba(0,0,0,0.2); transform:translateY(100%); transition:transform 0.3s ease; } #quoteBar.visible { transform:translateY(0); } #quoteBar .qb-info { font-size:1rem; } #quoteBar .qb-info strong { font-size:1.2rem; } #quoteBar .qb-actions { display:flex; gap:8px; } /* ── QUOTE MODAL ── */ .quote-modal { max-width:600px; } .quote-pros-list { display:flex; flex-wrap:wrap; gap:6px; margin-bottom:16px; } .quote-pro-chip { display:inline-flex; align-items:center; gap:6px; background:var(--primary-light); color:var(--primary-dark); padding:6px 12px; border-radius:20px; font-size:0.85rem; font-weight:500; } .quote-pro-chip .remove-pro { cursor:pointer; background:none; border:none; font-size:1rem; color:var(--primary-dark); opacity:0.6; padding:0; line-height:1; } .quote-pro-chip .remove-pro:hover { opacity:1; } .urgency-select { display:flex; gap:8px; margin-top:4px; } .urgency-opt { flex:1; text-align:center; padding:10px; border:2px solid var(--border); border-radius:10px; cursor:pointer; font-size:0.85rem; font-weight:500; transition:all 0.2s; background:white; } .urgency-opt:hover { border-color:var(--primary); } .urgency-opt.selected { border-color:var(--primary); background:var(--primary-light); color:var(--primary-dark); } .urgency-opt input { display:none; } /* ── QUOTE REQUEST CARDS (dashboard) ── */ .quote-req-card { background:white; border:1px solid var(--border); border-radius:var(--radius); padding:18px; margin-bottom:12px; transition:border-color 0.2s; } .quote-req-card:hover { border-color:var(--primary); } .quote-req-header { display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:8px; } .quote-req-client { font-weight:700; font-size:1rem; } .quote-req-urgency { padding:4px 10px; border-radius:6px; font-size:0.78rem; font-weight:600; } .urgency-normal { background:#d1fae5; color:#065f46; } .urgency-pronto { background:#fef3c7; color:#92400e; } .urgency-urgente { background:#fee2e2; color:#991b1b; } .quote-req-message { color:var(--text); font-size:0.92rem; margin:8px 0; padding:10px; background:#f8fafc; border-radius:8px; } .quote-req-meta { display:flex; gap:14px; color:var(--text-light); font-size:0.84rem; flex-wrap:wrap; } .quote-req-actions { display:flex; gap:8px; margin-top:12px; flex-wrap:wrap; } .quote-req-status { font-size:0.85rem; font-weight:600; padding:6px 14px; border-radius:8px; } .qstatus-responded { background:#d1fae5; color:#065f46; } .qstatus-rejected { background:#f1f5f9; color:var(--text-light); } @media (max-width:600px) { #quoteBar { flex-direction:column; gap:10px; text-align:center; padding:14px 16px; } #quoteBar .qb-actions { width:100%; justify-content:center; } .urgency-select { flex-direction:column; } } /* loading spinner */ .spinner { display:inline-block; width:18px; height:18px; border:2px solid rgba(255,255,255,0.3); border-top-color:white; border-radius:50%; animation:spin 0.6s linear infinite; } @keyframes spin { to { transform:rotate(360deg); } } /* autocomplete dropdown */ .autocomplete-items { position:absolute; border:1px solid var(--border); border-top:none; z-index:99; top:100%; left:0; right:0; max-height:240px; overflow-y:auto; background:white; border-radius:0 0 12px 12px; box-shadow:var(--shadow-lg); } .autocomplete-items div { padding:12px 16px; cursor:pointer; border-bottom:1px solid var(--border); font-size:0.9rem; transition:background 0.1s; } .autocomplete-items div:hover { background:var(--primary-light); color:var(--primary-dark); } .autocomplete-items div:last-child { border-bottom:none; } /* Smooth scroll */ html { scroll-behavior:smooth; } /* Focus visible para accesibilidad */ .btn:focus-visible, .nav-tab:focus-visible, .cat-chip:focus-visible, .btn-wa-primary:focus-visible, .btn-secondary:focus-visible { outline:2px solid var(--primary); outline-offset:2px; } /* Animacion de entrada para cards */ .pro-card { animation:fadeInUp 0.3s ease both; } @keyframes fadeInUp { from { opacity:0; transform:translateY(12px); } to { opacity:1; transform:translateY(0); } } /* ── FLOATING WHATSAPP BUTTON (solo mobile) ── */ .floating-wa { position:fixed; bottom:80px; right:16px; z-index:1003; width:56px; height:56px; border-radius:50%; background:var(--whatsapp); color:white; display:none; align-items:center; justify-content:center; font-size:1.6rem; box-shadow:0 4px 16px rgba(37,211,102,0.4); text-decoration:none; transition:all 0.2s; animation:floatingPulse 2s ease-in-out infinite; } .floating-wa:hover { transform:scale(1.1); box-shadow:0 6px 20px rgba(37,211,102,0.5); } @keyframes floatingPulse { 0%,100%{box-shadow:0 4px 16px rgba(37,211,102,0.4)} 50%{box-shadow:0 4px 24px rgba(37,211,102,0.6)} } @media (max-width:768px) { .floating-wa { display:flex; } } /* ── SOCIAL PROOF BANNER ── */ .social-proof-bar { background:white; border-bottom:1px solid var(--border); padding:10px 24px; overflow:hidden; position:relative; } .social-proof-inner { display:flex; gap:32px; align-items:center; justify-content:center; flex-wrap:wrap; max-width:900px; margin:0 auto; font-size:0.85rem; color:var(--text-light); font-weight:500; } .social-proof-item { display:flex; align-items:center; gap:6px; white-space:nowrap; } .social-proof-num { font-weight:800; color:var(--primary); font-size:1rem; } /* ── REVIEW HIGHLIGHT en la busqueda ── */ .pro-review-highlight { background:#fffbeb; border:1px solid #fef3c7; border-radius:8px; padding:8px 12px; margin-top:8px; font-size:0.82rem; color:#92400e; line-height:1.4; font-style:italic; } .pro-review-highlight strong { font-style:normal; } /* ── VERIFICACION BADGE mejorado ── */ .badge-guarantee { background:#ecfdf5; color:#065f46; padding:4px 10px; border-radius:6px; font-size:0.72rem; font-weight:600; letter-spacing:0.2px; } /style>/head>body>!-- ══════ ALERT CONTAINER ══════ -->div idalertBox classalert>/div>!-- ══════ NAVBAR ══════ -->nav classtopnav> div classnav-inner> a classlogo href# onclickshowPage(buscar)>🔧 plomerosspan>.com.ar/span>/a> div classnav-tabs> button classnav-tab active data-pagebuscar onclickshowPage(buscar)>🔍 Buscar/button> button classnav-tab data-pagepublicar onclickshowPage(publicar)>📢 Publicar problema/button> button classnav-tab data-pagepedidos onclickshowPage(pedidos)>📋 Mis pedidos/button> /div> div classnav-auth idnavAuth> div idauthLoggedOut> button classbtn btn-outline onclickshowPage(registrar)>Soy profesional/button> button classbtn btn-primary btn-sm onclickopenLoginModal()>Iniciar sesión/button> /div> div idauthLoggedIn styledisplay:none;> div classuser-dropdown> button classuser-btn onclicktoggleUserMenu()> span>👤/span> span iduserName>Profesional/span> span>▾/span> /button> div classuser-menu iduserMenu> a href# onclickshowPage(dashboard);closeUserMenu();>🏠 Mi perfil/a> a href# onclickhandleLogout()>🚪 Cerrar sesión/a> /div> /div> /div> /div> /div>/nav>!-- ══════ LOGIN MODAL ══════ -->div classmodal-overlay idloginModal> div classmodal> button classmodal-close onclickcloseLoginModal()>×/button> h2>🔐 Iniciar sesión/h2> form idloginForm> div classfield> label>Email/label> input typeemail idloginEmail required placeholdertu@email.com> /div> div classfield> label>Contraseña/label> input typepassword idloginPassword required placeholderTu contraseña minlength6> /div> div idloginError classfield error styledisplay:none;color:var(--danger);font-size:0.88rem;>/div> button typesubmit classbtn btn-primary btn-lg stylewidth:100%; idloginBtn>Iniciar sesión/button> p styletext-align:center;margin-top:14px;font-size:0.88rem;color:var(--text-light);> a href# onclickhandleForgotPassword() stylecolor:var(--primary);>¿Olvidaste tu contraseña?/a> /p> p styletext-align:center;margin-top:8px;font-size:0.88rem;color:var(--text-light);> ¿No tenés cuenta? a href# onclickcloseLoginModal();showPage(registrar); stylecolor:var(--primary);>Registrate como profesional/a> /p> /form> /div>/div>!-- ══════ REVIEW MODAL ══════ -->div classmodal-overlay idreviewModal> div classmodal> button classmodal-close onclickcloseReviewModal()>×/button> h2>⭐ Dejar una reseña/h2> p stylecolor:var(--text-light);margin-bottom:14px; idreviewProName>/p> form idreviewForm> input typehidden idreviewProId> div classfield> label>Tu puntuación/label> div classstar-input idstarInput> span data-val1 onclicksetStars(1)>★/span> span data-val2 onclicksetStars(2)>★/span> span data-val3 onclicksetStars(3)>★/span> span data-val4 onclicksetStars(4)>★/span> span data-val5 onclicksetStars(5)>★/span> /div> /div> div classfield> label>Tu nombre/label> input typetext idreviewerName required placeholderEj: María G.> /div> div classfield> label>Comentario/label> textarea idreviewComment placeholder¿Cómo fue tu experiencia? rows3>/textarea> /div> button typesubmit classbtn btn-primary btn-lg stylewidth:100%; idreviewBtn>Enviar reseña/button> /form> /div>/div>!-- ══════ PAGE: BUSCAR ══════ -->div classpage active idpage-buscar> div classhero-search> h1>Encontrá plomeros y profesionales verificados en Buenos Aires/h1> p classhero-subtitle>Contacto directo por WhatsApp. Sin intermediarios, sin comisiones./p> div classhero-trust-line>394+ profesionales disponibles ahora/div> div classsearch-bar> select idsvcFilter> option value>Todos los servicios/option> option valueplomería>🔧 Plomería/option> option valueelectricidad>⚡ Electricidad/option> option valuegas>🔥 Gas/option> option valuedestapaciones>🚿 Destapaciones/option> option valueaire acondicionado>❄️ Aire acondicionado/option> option valuecerrajería>🔑 Cerrajería/option> option valuepintura>🎨 Pintura/option> option valuealbañilería>🧱 Albañilería/option> /select> div styleposition:relative;flex:1;> input typetext idzoneFilter placeholderBarrio o zona... autocompleteoff> div idzoneAutocomplete classautocomplete-items styledisplay:none;>/div> /div> button classbtn btn-primary onclickapplyFilters()>Buscar/button> /div> div classcat-row> a classcat-chip active onclickfilterCat()>🔍 Todos/a> a classcat-chip emergency-chip onclickfilterEmergency()>🚨 Emergencias 24hs/a> a classcat-chip onclickfilterCat(plomería)>🔧 Plomeros/a> a classcat-chip onclickfilterCat(electricidad)>⚡ Electricistas/a> a classcat-chip onclickfilterCat(gas)>🔥 Gasistas/a> a classcat-chip onclickfilterCat(destapaciones)>🚿 Destapaciones/a> a classcat-chip onclickfilterCat(aire acondicionado)>❄️ Aire Acond./a> a classcat-chip onclickfilterCat(cerrajería)>🔑 Cerrajeros/a> a classcat-chip onclickfilterCat(pintura)>🎨 Pintores/a> a classcat-chip onclickfilterCat(albañilería)>🧱 Albañiles/a> /div> div classstats-row> div>div classstat-n idstatPros>—/div>div classstat-l>Profesionales/div>/div> div>div classstat-n idstatZones>—/div>div classstat-l>Zonas/div>/div> div>div classstat-n>8/div>div classstat-l>Categorías/div>/div> /div> /div> div classtrust-banner> div classtrust-content> span classtrust-main>✅ span idtrustCount>50+/span> profesionales verificados en Buenos Aires/span> div classtrust-points> span classtrust-point>💬 Contacto directo por WhatsApp/span> span classtrust-point>🚫 Sin intermediarios ni comisiones/span> span classtrust-point>⭐ Reviews reales de clientes/span> span classtrust-point>💰 100% gratis para vos/span> /div> /div> /div> !-- Social proof bar con metricas --> div classsocial-proof-bar> div classsocial-proof-inner> div classsocial-proof-item>span classsocial-proof-num idspPros>394/span> profesionales registrados/div> div classsocial-proof-item>span classsocial-proof-num idspReviews>37/span> reviews de clientes/div> div classsocial-proof-item>span classsocial-proof-num idspPages>809/span> perfiles publicados/div> /div> /div> div classresults-wrap> div> div classresults-head> div> h2>Profesionales disponibles/h2> span stylecolor:var(--text-light);font-size:0.88rem; idresCnt>/span> /div> select classsort-sel idsortSel onchangeapplyFilters()> option valuerating>Mejor valorados/option> option valuereviews>Más reseñas/option> option valuename>Nombre A-Z/option> option valuenewest>Más recientes/option> /select> /div> div idresList> !-- Skeleton loading mientras cargan los datos --> div classskeleton-card>div classskeleton-line long>/div>div classskeleton-line short>/div>div classskeleton-line medium>/div>div classskeleton-line btn>/div>/div> div classskeleton-card>div classskeleton-line long>/div>div classskeleton-line short>/div>div classskeleton-line medium>/div>div classskeleton-line btn>/div>/div> div classskeleton-card>div classskeleton-line long>/div>div classskeleton-line short>/div>div classskeleton-line medium>/div>div classskeleton-line btn>/div>/div> /div> div classload-more-container idloadMoreContainer styledisplay:none;> button classbtn btn-outline btn-lg onclickloadMorePros() idloadMoreBtn> Cargar más profesionales /button> /div> /div> div>div idmap>/div>/div> /div>/div>!-- ══════ PAGE: PUBLICAR PROBLEMA ══════ -->div classpage idpage-publicar> div classpost-page> h1>📢 Publica tu problema/h1> p>Describi que necesitas y los profesionales te contactan con su presupuesto./p> !-- Pasos del proceso --> div styledisplay:flex;gap:12px;margin-bottom:24px;flex-wrap:wrap;> div styleflex:1;min-width:120px;text-align:center;padding:12px;background:var(--primary-light);border-radius:10px;> div stylefont-size:1.2rem;font-weight:800;color:var(--primary);>1/div> div stylefont-size:0.82rem;color:var(--primary-dark);font-weight:500;>Describi el trabajo/div> /div> div styleflex:1;min-width:120px;text-align:center;padding:12px;background:#f1f5f9;border-radius:10px;> div stylefont-size:1.2rem;font-weight:800;color:var(--text-light);>2/div> div stylefont-size:0.82rem;color:var(--text-light);font-weight:500;>Recibi presupuestos/div> /div> div styleflex:1;min-width:120px;text-align:center;padding:12px;background:#f1f5f9;border-radius:10px;> div stylefont-size:1.2rem;font-weight:800;color:var(--text-light);>3/div> div stylefont-size:0.82rem;color:var(--text-light);font-weight:500;>Elegi al mejor/div> /div> /div> div classform-card> form idbidForm> div classfield> label>¿Que servicio necesitas?/label> select idbidService required> option value>Selecciona.../option> option valueplomería>🔧 Plomeria/option> option valueelectricidad>⚡ Electricidad/option> option valuegas>🔥 Gas matriculado/option> option valuedestapaciones>🚿 Destapaciones/option> option valueaire acondicionado>❄️ Aire acondicionado/option> option valuecerrajería>🔑 Cerrajeria/option> option valuepintura>🎨 Pintura/option> option valuealbañilería>🧱 Albanileria/option> option valueotro>🛠️ Otro/option> /select> /div> div classfield> label>Titulo corto/label> input typetext idbidTitle placeholderEj: Perdida en cano del bano required> span classhint>Ej: Cambio de canilla, Instalacion electrica/span> /div> div classfield> label>Describi el problema/label> textarea idbidDesc placeholderConta con detalle que pasa. Cuanto mas info des, mejor te van a poder cotizar.>/textarea> /div> div classfield> label>📍 ¿Donde?/label> input typetext idbidZone placeholderBarrio y direccion aprox (ej: Palermo, CABA) required> /div> div classfield> label>💰 ¿Tenes un presupuesto maximo? (opcional)/label> input typenumber idbidBudget placeholder$ monto> /div> div classfield> label>📱 Tu nombre/label> input typetext idbidContactName placeholderTu nombre required> /div> div classfield> label>📞 Tu telefono / WhatsApp/label> input typetel idbidContactPhone placeholder11 1234-5678 required> span classhint>Los profesionales te van a contactar por aca/span> /div> div classfield> label>📧 Tu email (opcional)/label> input typeemail idbidContactEmail placeholdertu@email.com> /div> div classfield> label>⏰ ¿Que tan urgente es?/label> select idbidUrgency> option valuenormal>🟢 Normal — cuando haya turno/option> option valuepronto>🟡 Pronto — esta semana/option> option valueurgente>🔴 Urgente — hoy o manana/option> /select> /div> button typesubmit classbtn btn-primary btn-lg stylewidth:100%;margin-top:8px; idbidSubmitBtn> 📢 Publicar pedido gratis /button> p styletext-align:center;margin-top:8px;font-size:0.82rem;color:var(--text-light);>Sin compromiso. Los profesionales te contactan directamente./p> /form> /div> /div>/div>!-- ══════ PAGE: MIS PEDIDOS ══════ -->div classpage idpage-pedidos> div classjobs-page> h1>📋 Mis pedidos/h1> div idjobsList>/div> /div>/div>!-- ══════ PAGE: REGISTRO PROFESIONAL ══════ -->div classpage idpage-registrar> div classregister-page> h1>🛠️ Registrate como profesional/h1> p stylecolor:var(--text-light);margin-bottom:16px;>Aparece en el mapa, recibi pedidos de clientes y hace crecer tu negocio./p> !-- Beneficios para profesionales --> div stylebackground:linear-gradient(135deg,#f0f9ff,#e0f2fe);border:1px solid var(--border);border-radius:var(--radius);padding:18px;margin-bottom:24px;> div styledisplay:grid;grid-template-columns:1fr 1fr;gap:10px;font-size:0.88rem;> div styledisplay:flex;align-items:center;gap:8px;>✅ strong>100% gratis/strong>/div> div styledisplay:flex;align-items:center;gap:8px;>📍 strong>Aparece en el mapa/strong>/div> div styledisplay:flex;align-items:center;gap:8px;>💬 strong>Contacto directo por WhatsApp/strong>/div> div styledisplay:flex;align-items:center;gap:8px;>📩 strong>Recibi pedidos de trabajo/strong>/div> /div> /div> div classform-card> form idregForm> div classfield> label>Email (para iniciar sesion)/label> input typeemail idregEmail placeholdertu@email.com required> /div> div classfield> label>Contrasena/label> input typepassword idregPassword placeholderMinimo 6 caracteres required minlength6> /div> div classfield> label>Repetir contrasena/label> input typepassword idregPassword2 placeholderRepeti la contrasena required minlength6> /div> hr stylemargin:20px 0;border:none;border-top:1px solid var(--border);> div classfield> label>Nombre completo / Empresa/label> input typetext idregName placeholderEj: Juan Perez Plomeria required> /div> div classfield> label>Telefono / WhatsApp/label> input typetel idregPhone placeholder+54 11 1234-5678 required> span classhint>Los clientes te van a contactar por este numero/span> /div> div idregError styledisplay:none;color:var(--danger);font-size:0.88rem;margin-bottom:12px;>/div> button typesubmit classbtn btn-primary btn-lg stylewidth:100%; idregBtn>Registrarme gratis/button> p styletext-align:center;margin-top:10px;font-size:0.82rem;color:var(--text-light);>Ya se registraron 394+ profesionales/p> /form> /div> /div>/div>!-- ══════ PAGE: DASHBOARD ══════ -->div classpage idpage-dashboard> div classdashboard-page> h1>🏠 Mi perfil profesional/h1> div classdash-stats> div classdash-stat> div classval iddashRating>—/div> div classlbl>⭐ Puntuación/div> /div> div classdash-stat> div classval iddashReviews>0/div> div classlbl>📝 Reseñas/div> /div> div classdash-stat> div classval iddashQuotes>0/div> div classlbl>📩 Cotizaciones/div> /div> /div> div classdash-section> h3>📋 Mi información/h3> p stylecolor:var(--text-light);margin-bottom:16px;>Tu perfil se muestra a los clientes que buscan servicios en tu zona./p> div iddashInfo styleline-height:1.8;> p>strong>Nombre:/strong> span iddashNameDisplay>-/span>/p> p>strong>Email:/strong> span iddashEmailDisplay>-/span>/p> p>strong>Teléfono:/strong> span iddashPhoneDisplay>-/span>/p> /div> /div> div classdash-section> h3>📩 Cotizaciones recibidas/h3> div iddashQuotesList> p stylecolor:var(--text-light);>Cargando cotizaciones.../p> /div> /div> div classdash-section> h3>⭐ Mis reseñas/h3> div iddashReviewsList> p stylecolor:var(--text-light);>Cargando reseñas.../p> /div> /div> /div>/div>!-- ══════ QUOTE BAR ══════ -->div idquoteBar> div classqb-info> 📩 strong idquoteCount>0/strong> profesionales seleccionados /div> div classqb-actions> button classbtn stylebackground:rgba(255,255,255,0.2);color:white;border:1px solid rgba(255,255,255,0.3); onclickclearSelection()>✕ Limpiar/button> button classbtn btn-accent btn-lg onclickopenQuoteModal()>📩 Pedir cotización/button> /div>/div>!-- ══════ QUOTE REQUEST MODAL ══════ -->div classmodal-overlay idquoteModal> div classmodal quote-modal> button classmodal-close onclickcloseQuoteModal()>×/button> h2>📩 Pedir cotización/h2> p stylecolor:var(--text-light);margin-bottom:16px;>Enviá tu pedido a estos profesionales y te van a contactar:/p> div classquote-pros-list idquoteProsList>/div> form idquoteForm> div classfield> label>¿Qué necesitás? */label> textarea idquoteMessage required placeholderDescribí el trabajo que necesitás. Cuanto más detalle, mejor presupuesto te van a dar. rows4>/textarea> /div> div classfield> label>Tu nombre */label> input typetext idquoteName required placeholderEj: María García> /div> div classfield> label>Tu teléfono / WhatsApp */label> input typetel idquotePhone required placeholderEj: 11 2345-6789> /div> div classfield> label>Tu zona / dirección aproximada */label> input typetext idquoteZone required placeholderEj: Palermo, CABA> /div> div classfield> label>¿Qué tan urgente es?/label> div classurgency-select idquoteUrgency> label classurgency-opt selected onclickselectUrgency(this,normal)> input typeradio namequrgency valuenormal checked> 🟢 Normal /label> label classurgency-opt onclickselectUrgency(this,pronto)> input typeradio namequrgency valuepronto> 🟡 Pronto /label> label classurgency-opt onclickselectUrgency(this,urgente)> input typeradio namequrgency valueurgente> 🔴 Urgente /label> /div> /div> button typesubmit classbtn btn-primary btn-lg stylewidth:100%; idquoteSubmitBtn> 📩 Enviar a span idquoteSubmitCount>0/span> profesionales /button> /form> /div>/div>!-- Mobile sticky CTA -->div idmobileCTA classmobile-sticky-cta> div classmobile-cta-content> div classmobile-cta-text> strong>¿Necesitas un profesional?/strong> span>Publica gratis tu problema/span> /div> a href# onclickshowPage(publicar);return false; classmobile-cta-btn> 📢 Publicar /a> /div>/div>!-- Boton flotante de WhatsApp para mobile -->a href# idfloatingWA classfloating-wa onclickscrollToFirstPro();return false; titleContactar profesional>💬/a>footer> div stylemax-width:800px;margin:0 auto;> p>strong stylefont-size:1.1rem;>plomeros.com.ar/strong>/p> p stylemargin-top:4px;font-size:0.92rem;color:rgba(255,255,255,0.85);>El directorio de profesionales del hogar mas grande de Buenos Aires./p> div styledisplay:flex;justify-content:center;gap:20px;margin-top:16px;flex-wrap:wrap;font-size:0.88rem;> a href# onclickshowPage(buscar);return false;>Buscar profesionales/a> a href# onclickshowPage(publicar);return false;>Publicar problema/a> a href# onclickshowPage(registrar);return false;>Registrarme como profesional/a> /div> p stylemargin-top:14px;font-size:0.88rem;>¿Sugerencias? a hrefmailto:hola@plomeros.com.ar>hola@plomeros.com.ar/a>/p> p stylemargin-top:8px;font-size:0.82rem;> a href/privacidad.html>Politica de privacidad/a> · a href/terminos.html>Terminos y condiciones/a> /p> p stylemargin-top:12px;font-size:0.78rem;>© 2026 plomeros.com.ar — Hecho con 🇦🇷 en Buenos Aires/p> /div>/footer>script>// ═══════ API CONFIG ═══════const API_BASE (window.location.hostname localhost || window.location.hostname 127.0.0.1) ? http://localhost:8090 : ;// ═══════ GLOBAL STATE ═══════let currentUser null;let currentProProfile null;let allPros ;let filtered ;let displayedPros ;let currentPage 1;let itemsPerPage 20;let emergencyFilter false;let map, markers ;let currentReviewStars 0;const SVC_ICON {plomería:🔧,electricidad:⚡,gas:🔥,destapaciones:🚿,aire acondicionado:❄️,cerrajería:🔑,pintura:🎨,albañilería:🧱};// ═══════ ZONE COORDS ═══════const ZONE_COORDS { palermo: { lat: -34.5795, lng: -58.4290 }, recoleta: { lat: -34.5870, lng: -58.3950 }, belgrano: { lat: -34.5620, lng: -58.4560 }, caballito: { lat: -34.6195, lng: -58.4400 }, almagro: { lat: -34.6100, lng: -58.4200 }, villa crespo: { lat: -34.5990, lng: -58.4380 }, flores: { lat: -34.6280, lng: -58.4630 }, balvanera: { lat: -34.6090, lng: -58.4010 }, san telmo: { lat: -34.6210, lng: -58.3700 }, barracas: { lat: -34.6440, lng: -58.3800 }, boedo: { lat: -34.6300, lng: -58.4150 }, monserrat: { lat: -34.6130, lng: -58.3790 }, san nicolás: { lat: -34.6050, lng: -58.3830 }, san nicolas: { lat: -34.6050, lng: -58.3830 }, núñez: { lat: -34.5490, lng: -58.4610 }, nunez: { lat: -34.5490, lng: -58.4610 }, villa urquiza: { lat: -34.5730, lng: -58.4900 }, villa devoto: { lat: -34.5990, lng: -58.5150 }, liniers: { lat: -34.6430, lng: -58.5220 }, once: { lat: -34.6090, lng: -58.4050 }, parque patricios: { lat: -34.6370, lng: -58.4020 }, la boca: { lat: -34.6350, lng: -58.3630 }, san cristóbal: { lat: -34.6230, lng: -58.4000 }, san cristobal: { lat: -34.6230, lng: -58.4000 }, constitución: { lat: -34.6260, lng: -58.3830 }, constitucion: { lat: -34.6260, lng: -58.3830 }, floresta: { lat: -34.6300, lng: -58.4620 }, colegiales: { lat: -34.5745, lng: -58.4524 }, villa del parque: { lat: -34.6060, lng: -58.4885 }, chacarita: { lat: -34.5870, lng: -58.4540 }, mataderos: { lat: -34.6572, lng: -58.5098 }, villa lugano: { lat: -34.6759, lng: -58.4706 }, parque chacabuco: { lat: -34.6354, lng: -58.4388 }, saavedra: { lat: -34.5550, lng: -58.4870 }, monte castro: { lat: -34.6050, lng: -58.5050 }, versalles: { lat: -34.6290, lng: -58.5190 }, villa luro: { lat: -34.6353, lng: -58.5006 }, villa santa rita: { lat: -34.6100, lng: -58.4800 }, villa general mitre: { lat: -34.6119, lng: -58.4566 }, parque avellaneda: { lat: -34.6443, lng: -58.4789 }, villa soldati: { lat: -34.6713, lng: -58.4471 }, villa real: { lat: -34.6196, lng: -58.5234 }, parque chas: { lat: -34.5857, lng: -58.4837 }, villa ortúzar: { lat: -34.5828, lng: -58.4596 }, villa ortuzar: { lat: -34.5828, lng: -58.4596 }, agronomía: { lat: -34.5950, lng: -58.4900 }, agronomia: { lat: -34.5950, lng: -58.4900 }, retiro: { lat: -34.5920, lng: -58.3740 }, puerto madero: { lat: -34.6170, lng: -58.3630 }, villa pueyrredón: { lat: -34.5830, lng: -58.5050 }, villa pueyrredon: { lat: -34.5830, lng: -58.5050 }, microcentro: { lat: -34.6037, lng: -58.3816 }, tribunales: { lat: -34.6020, lng: -58.3850 }, congreso: { lat: -34.6090, lng: -58.3920 }, caba: { lat: -34.6083, lng: -58.3712 }, capital federal: { lat: -34.6083, lng: -58.3712 }, buenos aires: { lat: -34.6083, lng: -58.3712 },};function getZoneCoords(zone) { if (!zone) return { lat: -34.6083, lng: -58.3712 }; const z zone.toLowerCase().trim(); if (ZONE_COORDSz) return ZONE_COORDSz; for (const key, coords of Object.entries(ZONE_COORDS)) { if (z.includes(key) || key.includes(z)) return coords; } return { lat: -34.6083, lng: -58.3712 };}// ═══════ API HELPERS ═══════async function apiCall(endpoint, options {}) { const url API_BASE + endpoint; const defaultHeaders { Content-Type: application/json }; if (currentUser && currentUser.token) { defaultHeadersAuthorization `Bearer ${currentUser.token}`; } const config { ...options, headers: { ...defaultHeaders, ...options.headers } }; const response await fetch(url, config); if (!response.ok) { const error await response.json().catch(() > ({ detail: Error desconocido })); throw new Error(error.detail || error.message || `Error ${response.status}`); } return response.json();}// ═══════ ANALYTICS ═══════window._sessionId sess_ + Date.now() + _ + Math.random().toString(36).substr(2,9);async function trackEvent(event, data {}) { try { await apiCall(/api/analytics/event, { method: POST, body: JSON.stringify({ event, data, page: window.location.pathname, session_id: window._sessionId }) }); } catch(e) { /* silent fail */ }}// ═══════ AUTH ═══════function loadAuthState() { const stored localStorage.getItem(plomeros_auth); if (stored) { try { currentUser JSON.parse(stored); updateAuthUI(); // Verify token is still valid verifyAuth(); } catch(e) { localStorage.removeItem(plomeros_auth); } }}async function verifyAuth() { if (!currentUser || !currentUser.token) return; try { const data await apiCall(`/api/auth/me?token${encodeURIComponent(currentUser.token)}`); currentUser.user data; updateAuthUI(); } catch(e) { handleLogout(); }}function updateAuthUI() { const loggedOut document.getElementById(authLoggedOut); const loggedIn document.getElementById(authLoggedIn); if (currentUser && currentUser.token) { loggedOut.style.display none; loggedIn.style.display block; document.getElementById(userName).textContent currentUser.user?.name || currentUser.user?.email || Usuario; } else { loggedOut.style.display flex; loggedIn.style.display none; }}window.openLoginModal function() { document.getElementById(loginModal).classList.add(show); document.getElementById(loginError).style.display none;};window.closeLoginModal function() { document.getElementById(loginModal).classList.remove(show);};document.getElementById(loginForm).addEventListener(submit, async function(e) { e.preventDefault(); const btn document.getElementById(loginBtn); const email document.getElementById(loginEmail).value; const password document.getElementById(loginPassword).value; const errEl document.getElementById(loginError); btn.disabled true; btn.innerHTML span classspinner>/span> Ingresando...; errEl.style.display none; try { const data await apiCall(/api/auth/login, { method: POST, body: JSON.stringify({ email, password }) }); currentUser data; localStorage.setItem(plomeros_auth, JSON.stringify(data)); updateAuthUI(); closeLoginModal(); showAlert(✅ ¡Bienvenido de vuelta!); if (data.user.role professional) { showPage(dashboard); } } catch(err) { errEl.textContent err.message; errEl.style.display block; } finally { btn.disabled false; btn.textContent Iniciar sesión; }});window.handleForgotPassword function() { showAlert(Contactá a hola@plomeros.com.ar para restablecer tu contraseña, info);};window.handleLogout function() { currentUser null; currentProProfile null; localStorage.removeItem(plomeros_auth); updateAuthUI(); showAlert(👋 Sesión cerrada); showPage(buscar);};window.toggleUserMenu function() { document.getElementById(userMenu).classList.toggle(show);};window.closeUserMenu function() { document.getElementById(userMenu).classList.remove(show);};document.addEventListener(click, (e) > { if (!e.target.closest(.user-dropdown)) { document.getElementById(userMenu)?.classList.remove(show); }});// ═══════ REGISTRATION ═══════document.getElementById(regForm).addEventListener(submit, async function(e) { e.preventDefault(); const btn document.getElementById(regBtn); const errEl document.getElementById(regError); const email document.getElementById(regEmail).value; const password document.getElementById(regPassword).value; const password2 document.getElementById(regPassword2).value; const name document.getElementById(regName).value.trim(); const phone document.getElementById(regPhone).value.trim(); errEl.style.display none; if (password ! password2) { errEl.textContent Las contraseñas no coinciden; errEl.style.display block; return; } btn.disabled true; btn.innerHTML span classspinner>/span> Registrando...; try { const data await apiCall(/api/auth/register, { method: POST, body: JSON.stringify({ email, password, name, phone, role: professional }) }); currentUser data; localStorage.setItem(plomeros_auth, JSON.stringify(data)); updateAuthUI(); trackEvent(registration, { role: professional }); showAlert(✅ ¡Registrado exitosamente! Bienvenido a plomeros.com.ar); this.reset(); showPage(dashboard); } catch(err) { errEl.textContent err.message; errEl.style.display block; } finally { btn.disabled false; btn.textContent Registrarme gratis; }});// ═══════ UTILITIES ═══════function showAlert(msg, type success) { const box document.getElementById(alertBox); box.className `alert alert-${type}`; box.textContent msg; box.classList.add(show); setTimeout(() > box.classList.remove(show), 4000);}function slugify(text) { return text.toLowerCase() .normalize(NFD).replace(/\u0300-\u036f/g, ) .replace(/^a-z0-9+/g, -) .replace(/(^-|-$)/g, );}// ═══════ NAVIGATION ═══════window.showPage function(name) { document.querySelectorAll(.page).forEach(p > p.classList.remove(active)); const page document.getElementById(page- + name); if (page) page.classList.add(active); document.querySelectorAll(.nav-tab).forEach(t > { t.classList.toggle(active, t.dataset.page name); }); window.scrollTo(0, 0); if (name buscar && map) setTimeout(() > map.invalidateSize(), 100); if (name pedidos) renderJobs(); if (name dashboard) loadDashboard();};// ═══════ MAP & DATA ═══════function initMap() { map L.map(map).setView(-34.6083, -58.3712, 12); L.tileLayer(https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png, { attribution:© OpenStreetMap }).addTo(map);}async function loadData() { try { const params new URLSearchParams({ page: 1, per_page: 500 }); const data await apiCall(`/api/professionals?${params}`); allPros data.items || ; document.getElementById(statPros).textContent allPros.length; // Get zones count const zonesData await apiCall(/api/professionals/zones); document.getElementById(statZones).textContent zonesData.length || 0; // Actualizar contadores de social proof const verifiedCount allPros.filter(p > p.verified || p.rating > 0).length; document.getElementById(trustCount).textContent verifiedCount + +; const spPros document.getElementById(spPros); if (spPros) spPros.textContent allPros.length; const totalReviews allPros.reduce((sum, p) > sum + (p.reviews_count || 0), 0); const spReviews document.getElementById(spReviews); if (spReviews && totalReviews > 0) spReviews.textContent totalReviews; // Track page view trackEvent(page_view, { page: home, referrer: document.referrer }); initMobileCTA(); handleUrlParameters(); applyFilters(); } catch(err) { console.error(Error loading data:, err); showAlert(Error cargando datos, error); }}function handleUrlParameters() { const urlParams new URLSearchParams(window.location.search); const service urlParams.get(service); const zone urlParams.get(zone); const sort urlParams.get(sort); const publicar urlParams.get(publicar); // Restore filters from URL if (service) { const el document.getElementById(svcFilter); if (el) el.value service; } if (zone) { const el document.getElementById(zoneFilter); if (el) el.value zone; } if (sort) { const el document.getElementById(sortSel); if (el) el.value sort; } if (publicar 1) { showPage(publicar); if (service) { document.getElementById(bidService).value service; } } else if (service || zone || sort) { if (service) { document.querySelectorAll(.cat-chip).forEach(c > c.classList.remove(active)); const serviceChip document.querySelector(`onclickfilterCat(${service})`); if (serviceChip) { serviceChip.classList.add(active); } } applyFilters(); }}function initMobileCTA() { const mobileCTA document.getElementById(mobileCTA); let lastScrollTop 0; let ticking false; function updateCTA() { const scrollTop window.pageYOffset; const heroHeight document.querySelector(.hero-search)?.offsetHeight || 500; const quoteBarVisible document.getElementById(quoteBar)?.classList.contains(visible) || false; if (window.innerWidth 768) { if (scrollTop > heroHeight && !quoteBarVisible) { mobileCTA.classList.add(show); } else { mobileCTA.classList.remove(show); } } else { mobileCTA.classList.remove(show); } lastScrollTop scrollTop; ticking false; } function requestTick() { if (!ticking) { requestAnimationFrame(updateCTA); ticking true; } } window.addEventListener(scroll, requestTick); window.addEventListener(resize, updateCTA);}function isEmergencyPro(pro) { const desc (pro.description || ).toLowerCase(); const name (pro.name || ).toLowerCase(); return desc.includes(emergencia) || desc.includes(24hs) || desc.includes(24 hs) || name.includes(emergencia) || name.includes(24hs) || name.includes(24 hs);}window.filterEmergency function() { emergencyFilter !emergencyFilter; const chip document.querySelector(.emergency-chip); if (emergencyFilter) { chip.classList.add(active); document.querySelectorAll(.cat-chip:not(.emergency-chip)).forEach(c > c.classList.remove(active)); } else { chip.classList.remove(active); document.querySelector(.cat-chiponclickfilterCat(\\)).classList.add(active); } applyFilters(); trackEvent(filter_emergency, { enabled: emergencyFilter });};window.loadMorePros function() { currentPage++; renderList(); trackEvent(load_more, { page: currentPage });};window.applyFilters function() { const svc document.getElementById(svcFilter).value.toLowerCase(); const zone document.getElementById(zoneFilter).value.toLowerCase().trim(); const sort document.getElementById(sortSel).value; currentPage 1; filtered allPros.filter(p > { if (!p.phone || p.phone.trim() ) return false; const ms !svc || (p.services||).some(s>s.toLowerCase().includes(svc)); const mz !zone || (p.zone||).toLowerCase().includes(zone) || (p.address||).toLowerCase().includes(zone); const me !emergencyFilter || isEmergencyPro(p); return ms && mz && me; }); filtered.sort((a,b) > { if (a.verified && !b.verified) return -1; if (!a.verified && b.verified) return 1; if (sort rating) return (b.rating||0)-(a.rating||0); if (sort reviews) return (b.reviews_count||0)-(a.reviews_count||0); if (sort name) return (a.name||).localeCompare(b.name||); if (sort newest) return (b.id||0)-(a.id||0); return 0; }); renderList(filtered); updateMarkers(filtered); if (svc || zone) { trackEvent(search, { service: svc, zone: zone, results: filtered.length }); } // Persist filters in URL const params new URLSearchParams(); const svcVal document.getElementById(svcFilter)?.value; const zoneVal document.getElementById(zoneFilter)?.value; const sortVal document.getElementById(sortSel)?.value; if (svcVal) params.set(service, svcVal); if (zoneVal) params.set(zone, zoneVal); if (sortVal && sortVal ! rating) params.set(sort, sortVal); const newUrl window.location.pathname + (params.toString() ? ? + params.toString() : ); history.replaceState(null, , newUrl);};window.filterCat function(svc) { document.getElementById(svcFilter).value svc; document.querySelectorAll(.cat-chip).forEach(c>c.classList.remove(active)); event.target.closest(.cat-chip).classList.add(active); emergencyFilter false; applyFilters();};function renderList(pros filtered) { const el document.getElementById(resList); const loadMoreContainer document.getElementById(loadMoreContainer); const totalItems pros.length; const endIndex currentPage * itemsPerPage; const itemsToShow pros.slice(0, endIndex); const hasMore endIndex totalItems; document.getElementById(resCnt).textContent `Mostrando ${itemsToShow.length} de ${totalItems} resultado${totalItems!1?s:}`; if (!totalItems) { el.innerHTML div classempty>div classe-icon>🔍/div>h3>Sin resultados/h3>p>Probá otra zona o servicio/p>/div>; loadMoreContainer.style.display none; return; } if (hasMore) { loadMoreContainer.style.display block; document.getElementById(loadMoreBtn).textContent `Cargar más profesionales (${totalItems - itemsToShow.length} restantes)`; } else { loadMoreContainer.style.display none; } el.innerHTML itemsToShow.map((p,i) > { const ratingVal Number(p.rating) || 0; const fullStars Math.floor(ratingVal); const starIcons ratingVal > 0 ? ★.repeat(fullStars) + (ratingVal % 1 > 0.5 ? ½ : ) + ☆.repeat(5 - Math.ceil(ratingVal)) : ; const tags (p.services||).map(s > `span classtag>${SVC_ICONs.toLowerCase()||🛠️} ${s}/span>`).join(); const ph (p.phone||).replace(/\D/g,); const wa ph.startsWith(54)?ph:54+ph; const proId p.id || p.slug || slugify(p.name || ); const safeName (p.name||).replace(//g, \\); const profileSlug slugify((p.name||) + + (p.zone||)); const profileUrl /profesional/ + profileSlug + .html; const mapsQuery encodeURIComponent((p.name||) + + (p.zone||) + Buenos Aires); const mapsUrl p.lat && p.lng ? https://www.google.com/maps/search/ + mapsQuery + /@ + p.lat + , + p.lng + ,17z : https://www.google.com/maps/search/ + mapsQuery; // Descripcion corta para preview const desc (p.description||).trim(); const descPreview desc ? `div classpro-desc-preview>${desc}/div>` : ; // Review highlight: mostrar snippet de la mejor review const latestReview (p.latest_review_text || p.google_review_text || ).trim(); const reviewHighlight (latestReview && latestReview.length > 10) ? `div classpro-review-highlight>${latestReview.substring(0, 120)}${latestReview.length > 120 ? ... : } strong>— Cliente verificado/strong>/div>` : ; // Mensaje de WhatsApp pre-armado const waMsg encodeURIComponent(Hola + (p.name||) + , te contacto desde plomeros.com.ar. Necesito un presupuesto para ); return `div classpro-card${p.verified? verified-card:} onclicktrackEvent(pro_card_click,{proId:${proId},proName:${safeName}});window.open(${profileUrl},_blank)> div classpro-top> div> a href${profileUrl} target_blank classpro-name-link onclickevent.stopPropagation()>div classpro-name>${p.name||Profesional}${isEmergencyPro(p) ? span classemergency-badge>🚨 24HS/span> : }/div>/a> div classpro-zone>📍 ${p.zone||p.address||Buenos Aires}/div> /div> div styledisplay:flex;gap:4px;flex-wrap:wrap;> ${p.verified?span classpro-badge badge-verified>✅ Verificado/span>:} ${ratingVal>4&&p.reviews_count>0?span classpro-badge badge-rec>⭐ Recomendado/span>:} /div> /div> ${descPreview} div classpro-tags>${tags}/div> div classpro-meta> ${ratingVal>0?`span>span classpro-rating-stars>${starIcons}/span> span classpro-rating-num>${ratingVal.toFixed(1)}/span>/span>`:} ${p.reviews_count?`span>${p.reviews_count} reseña${p.reviews_count>1?s:}/span>`:} /div> ${reviewHighlight} div classpro-btns> ${p.phone?`a hrefhttps://wa.me/${wa}?text${waMsg} target_blank classbtn-wa-primary onclickevent.stopPropagation();trackEvent(whatsapp_click,{proId:${proId},proName:${safeName}})>💬 Contactar por WhatsApp/a>`:} div classpro-btns-secondary> a hreftel:${p.phone} classbtn-secondary onclickevent.stopPropagation();trackEvent(call_click,{proId:${proId},proName:${safeName}}) titleLlamar>📞 Llamar/a> a href${mapsUrl} target_blank classbtn-secondary onclickevent.stopPropagation();trackEvent(maps_click,{proId:${proId},proName:${safeName}}) titleVer en Google Maps>📍 Maps/a> a href${profileUrl} target_blank classbtn-secondary onclickevent.stopPropagation() titleVer perfil completo>👤 Perfil/a> button classbtn-secondary onclickevent.stopPropagation();openReviewModal(${proId},${safeName}) titleDejar reseña>⭐ Reseña/button> label classpro-select idsel-${proId} onclickevent.stopPropagation(); onmousedowntoggleProSelect(${proId},${safeName},this)> span classcheck-icon>/span> span>Seleccionar/span> /label> /div> /div> /div>`; }).join();}function updateMarkers(pros) { markers.forEach(m > map.removeLayer(m)); markers ; if (window.clusterGroup) map.removeLayer(window.clusterGroup); window.clusterGroup L.markerClusterGroup({ maxClusterRadius: 50, spiderfyOnMaxZoom: true, showCoverageOnHover: false, iconCreateFunction: function(cluster) { const count cluster.getChildCount(); let size small; if (count > 10) size medium; if (count > 30) size large; return L.divIcon({ html: div stylebackground:var(--primary);color:white;width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px;box-shadow:0 2px 8px rgba(0,0,0,0.3);> + count + /div>, className: , iconSize: 40, 40 }); } }); const bounds ; pros.forEach((p,i) > { if (!p.lat||!p.lng) { const coords getZoneCoords(p.zone); p.lat coords.lat; p.lng coords.lng; } const icon L.divIcon({ className:, html:div stylebackground:var(--primary);color:white;width:30px;height:30px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:15px;box-shadow:0 2px 6px rgba(0,0,0,0.3);> + (SVC_ICONp.services?.0?.toLowerCase()||🔧) + /div>, iconSize:30,30, iconAnchor:15,15 }); const mk L.marker(p.lat,p.lng,{icon}).bindPopup(strong>+p.name+/strong>br>+(p.zone||)+br>+(p.phone?a hreftel:+p.phone+>+p.phone+/a>:)); mk.on(click, function() { focusMap(i); }); window.clusterGroup.addLayer(mk); markers.push(mk); bounds.push(p.lat,p.lng); }); map.addLayer(window.clusterGroup); if (bounds.length) { try { map.fitBounds(bounds, {padding:30,30, maxZoom:14}); } catch(e) {} }}window.focusMap function(i) { const p filteredi; if (p?.lat&&p?.lng) { map.setView(p.lat,p.lng,15); markersi?.openPopup(); }};// ═══════ AUTOCOMPLETE ═══════let autocompleteTimer;document.getElementById(zoneFilter).addEventListener(input, function(e) { const query e.target.value.trim(); const dropdown document.getElementById(zoneAutocomplete); clearTimeout(autocompleteTimer); if (query.length 2) { dropdown.style.display none; return; } autocompleteTimer setTimeout(async () > { try { const data await apiCall(`/api/search/autocomplete?q${encodeURIComponent(query)}`); if (data.length > 0) { dropdown.innerHTML data.slice(0, 5).map(item > { const label item.value || item; const extra item.count ? ` small stylecolor:#999>(${item.count})/small>` : ; const icon item.type service ? 🔧 : item.type professional ? 👤 : 📍; return `div onclickselectZone(${(item.value||item).replace(//g, \\)})>${icon} ${label}${extra}/div>`; }).join(); dropdown.style.display block; } else { dropdown.style.display none; } } catch(e) { dropdown.style.display none; } }, 300);});window.selectZone function(zone) { document.getElementById(zoneFilter).value zone; document.getElementById(zoneAutocomplete).style.display none; applyFilters();};document.addEventListener(click, (e) > { if (!e.target.closest(#zoneFilter)) { document.getElementById(zoneAutocomplete).style.display none; }});// ═══════ REVIEWS ═══════window.openReviewModal function(proId, proName) { document.getElementById(reviewProId).value proId; document.getElementById(reviewProName).textContent `Para: ${proName}`; document.getElementById(reviewModal).classList.add(show); currentReviewStars 0; document.querySelectorAll(#starInput span).forEach(s > s.classList.remove(active)); document.getElementById(reviewForm).reset(); document.getElementById(reviewProId).value proId;};window.closeReviewModal function() { document.getElementById(reviewModal).classList.remove(show);};window.setStars function(n) { currentReviewStars n; document.querySelectorAll(#starInput span).forEach(s > { s.classList.toggle(active, parseInt(s.dataset.val) n); });};document.getElementById(reviewForm).addEventListener(submit, async function(e) { e.preventDefault(); if (currentReviewStars 0) { showAlert(Seleccioná una puntuación, error); return; } const btn document.getElementById(reviewBtn); const proId document.getElementById(reviewProId).value; const reviewerName document.getElementById(reviewerName).value.trim(); const comment document.getElementById(reviewComment).value.trim(); btn.disabled true; btn.innerHTML span classspinner>/span> Enviando...; try { // Get the professionals numeric ID const pro allPros.find(p > String(p.id) String(proId) || p.slug proId); const professionalId pro ? pro.id : parseInt(proId); await apiCall(/api/reviews, { method: POST, body: JSON.stringify({ professional_id: professionalId, rating: currentReviewStars, comment: comment, author_name: reviewerName }) }); trackEvent(review_submit, { proId: proId, rating: currentReviewStars }); showAlert(✅ ¡Gracias por tu reseña!); closeReviewModal(); loadData(); } catch(err) { showAlert(Error enviando reseña: + err.message, error); } finally { btn.disabled false; btn.textContent Enviar reseña; }});// ═══════ JOBS ═══════document.getElementById(bidForm).addEventListener(submit, async function(e) { e.preventDefault(); const btn document.getElementById(bidSubmitBtn); btn.disabled true; btn.innerHTML span classspinner>/span> Publicando...; try { const job await apiCall(/api/jobs, { method: POST, body: JSON.stringify({ title: document.getElementById(bidTitle).value, description: document.getElementById(bidDesc).value, service: document.getElementById(bidService).value, zone: document.getElementById(bidZone).value, urgency: document.getElementById(bidUrgency).value, budget: document.getElementById(bidBudget).value ? parseFloat(document.getElementById(bidBudget).value) : null, contact_name: document.getElementById(bidContactName).value, contact_phone: document.getElementById(bidContactPhone).value, contact_email: document.getElementById(bidContactEmail).value || null }) }); const myJobs JSON.parse(localStorage.getItem(plomeros_my_jobs) || ); myJobs.unshift(job.id); localStorage.setItem(plomeros_my_jobs, JSON.stringify(myJobs)); trackEvent(job_post, { service: job.service, zone: job.zone }); showAlert(✅ ¡Pedido publicado! Los profesionales van a poder verlo.); this.reset(); showPage(pedidos); } catch(err) { showAlert(Error publicando: + err.message, error); } finally { btn.disabled false; btn.textContent 📢 Publicar pedido; }});async function renderJobs() { const el document.getElementById(jobsList); const myJobIds JSON.parse(localStorage.getItem(plomeros_my_jobs) || ); if (!myJobIds.length) { el.innerHTML `div classempty> div classe-icon>📋/div> h3>No tenés pedidos todavía/h3> p>Publicá tu primer problema y empezá a recibir cotizaciones./p> button classbtn btn-primary stylemargin-top:16px; onclickshowPage(publicar)>📢 Publicar problema/button> /div>`; return; } el.innerHTML div classempty>div classspinner styleborder-color:var(--primary);border-top-color:var(--primary-dark);width:30px;height:30px;>/div>p stylemargin-top:10px;>Cargando pedidos.../p>/div>; try { const jobs ; for (const jobId of myJobIds.slice(0, 20)) { try { const job await apiCall(`/api/jobs/${jobId}`); jobs.push(job); } catch(e) { console.warn(Could not load job, jobId, e); } } if (!jobs.length) { el.innerHTML `div classempty> div classe-icon>📋/div> h3>No tenés pedidos todavía/h3> p>Publicá tu primer problema y empezá a recibir cotizaciones./p> button classbtn btn-primary stylemargin-top:16px; onclickshowPage(publicar)>📢 Publicar problema/button> /div>`; return; } el.innerHTML jobs.map((j) > { const urgIcon j.urgencyurgente?🔴:j.urgencypronto?🟡:🟢; const statusCls j.status open ? open : quoted; const statusTxt j.status open ? Abierto : j.status; const date j.created_at ? new Date(j.created_at).toLocaleDateString(es-AR) : Reciente; return `div classjob-card> div classjob-header> div> div classjob-title>${SVC_ICONj.service||🛠️} ${j.title || Pedido}/div> div classjob-meta>${urgIcon} ${j.urgency || normal} · 📍 ${j.zone || } · ${date}/div> /div> span classjob-status status-${statusCls}>${statusTxt}/span> /div> ${j.description?`div classjob-desc>${j.description}/div>`:} /div>`; }).join(); } catch(err) { el.innerHTML `div classempty>div classe-icon>⚠️/div>h3>Error cargando pedidos/h3>p>${err.message}/p>/div>`; }}// ═══════ QUOTE REQUEST SYSTEM ═══════let selectedPros new Map();window.toggleProSelect function(proId, proName, labelEl) { event.preventDefault(); if (selectedPros.has(proId)) { selectedPros.delete(proId); labelEl.classList.remove(checked); } else { selectedPros.set(proId, proName); labelEl.classList.add(checked); } updateQuoteBar();};function updateQuoteBar() { const bar document.getElementById(quoteBar); const count selectedPros.size; document.getElementById(quoteCount).textContent count; if (count > 0) { bar.classList.add(visible); } else { bar.classList.remove(visible); }}window.clearSelection function() { selectedPros.clear(); document.querySelectorAll(.pro-select.checked).forEach(el > el.classList.remove(checked)); updateQuoteBar();};window.openQuoteModal function() { if (selectedPros.size 0) return; const listEl document.getElementById(quoteProsList); listEl.innerHTML ; selectedPros.forEach((name, id) > { listEl.innerHTML + `div classquote-pro-chip> 🔧 ${name} button typebutton classremove-pro onclickremoveProFromQuote(${id}) titleQuitar>×/button> /div>`; }); document.getElementById(quoteSubmitCount).textContent selectedPros.size; document.getElementById(quoteModal).classList.add(show);};window.closeQuoteModal function() { document.getElementById(quoteModal).classList.remove(show);};window.removeProFromQuote function(proId) { selectedPros.delete(proId); const label document.getElementById(sel- + proId); if (label) label.classList.remove(checked); updateQuoteBar(); if (selectedPros.size 0) { closeQuoteModal(); return; } openQuoteModal();};window.selectUrgency function(el, val) { document.querySelectorAll(#quoteUrgency .urgency-opt).forEach(o > o.classList.remove(selected)); el.classList.add(selected); el.querySelector(input).checked true;};document.getElementById(quoteForm).addEventListener(submit, async function(e) { e.preventDefault(); if (selectedPros.size 0) return; const btn document.getElementById(quoteSubmitBtn); btn.disabled true; btn.innerHTML span classspinner>/span> Enviando...; const message document.getElementById(quoteMessage).value.trim(); const clientName document.getElementById(quoteName).value.trim(); const clientPhone document.getElementById(quotePhone).value.trim(); const clientZone document.getElementById(quoteZone).value.trim(); const urgency document.querySelector(#quoteUrgency input:checked)?.value || normal; const proIds Array.from(selectedPros.keys()); try { for (const proId of proIds) { const pro allPros.find(p > String(p.id) String(proId) || p.slug proId); const professionalId pro ? pro.id : parseInt(proId); await apiCall(/api/quotes, { method: POST, body: JSON.stringify({ job_id: null, professional_id: professionalId, price: null, message: `${message}\n\nContacto: ${clientName}\nTeléfono: ${clientPhone}\nZona: ${clientZone}\nUrgencia: ${urgency}` }) }); } trackEvent(quote_request, { proCount: proIds.length, urgency: urgency }); showAlert(`✅ ¡Cotización enviada a ${proIds.length} profesional${proIds.length > 1 ? es : }!`); closeQuoteModal(); clearSelection(); this.reset(); document.querySelectorAll(#quoteUrgency .urgency-opt).forEach(o > o.classList.remove(selected)); document.querySelector(#quoteUrgency .urgency-opt).classList.add(selected); } catch(err) { showAlert(Error enviando cotización: + err.message, error); } finally { btn.disabled false; btn.innerHTML `📩 Enviar a span idquoteSubmitCount>${selectedPros.size}/span> profesionales`; }});// ═══════ DASHBOARD ═══════async function loadDashboard() { if (!currentUser || !currentUser.token) { showPage(buscar); openLoginModal(); return; } try { const user currentUser.user; document.getElementById(dashNameDisplay).textContent user.name || -; document.getElementById(dashEmailDisplay).textContent user.email || -; document.getElementById(dashPhoneDisplay).textContent user.phone || -; // Try to find professional profile const proData await apiCall(`/api/professionals?search${encodeURIComponent(user.email)}`); if (proData.items && proData.items.length > 0) { currentProProfile proData.items0; document.getElementById(dashRating).textContent currentProProfile.rating ? currentProProfile.rating.toFixed(1) : —; document.getElementById(dashReviews).textContent currentProProfile.reviews_count || 0; // Load reviews loadDashboardReviews(currentProProfile.id); loadDashboardQuotes(currentProProfile.id); } else { document.getElementById(dashRating).textContent —; document.getElementById(dashReviews).textContent 0; document.getElementById(dashQuotes).textContent 0; document.getElementById(dashReviewsList).innerHTML p stylecolor:var(--text-light);>No se encontró tu perfil profesional./p>; document.getElementById(dashQuotesList).innerHTML p stylecolor:var(--text-light);>No se encontró tu perfil profesional./p>; } } catch(err) { console.error(Dashboard error:, err); showAlert(Error cargando el perfil, error); }}async function loadDashboardReviews(proId) { const el document.getElementById(dashReviewsList); try { const data await apiCall(`/api/reviews?professional_id${proId}`); const items Array.isArray(data) ? data : (data.items || ); if (items.length 0) { el.innerHTML p stylecolor:var(--text-light);>Todavía no tenés reseñas./p>; return; } el.innerHTML items.map(r > { const stars ⭐.repeat(Math.min(5, r.rating || 0)); const date r.created_at ? new Date(r.created_at).toLocaleDateString(es-AR) : ; return `div classreview-item> div classreview-stars>${stars}/div> div classreview-author>${r.author_name || Anónimo}/div> div classreview-text>${r.comment || }/div> div classreview-date>${date}/div> /div>`; }).join(); } catch(err) { console.error(Reviews error:, err); el.innerHTML p stylecolor:var(--text-light);>Error cargando reseñas/p>; }}async function loadDashboardQuotes(proId) { const el document.getElementById(dashQuotesList); try { const data await apiCall(`/api/quotes?professional_id${proId}`); const items Array.isArray(data) ? data : (data.items || ); if (items.length 0) { el.innerHTML p stylecolor:var(--text-light);>No tenés cotizaciones pendientes./p>; document.getElementById(dashQuotes).textContent 0; return; } document.getElementById(dashQuotes).textContent items.length; el.innerHTML items.map(q > { const date q.created_at ? new Date(q.created_at).toLocaleDateString(es-AR) : Reciente; const statusLabel q.status accepted ? ✅ Aceptada : q.status rejected ? ❌ Rechazada : ⏳ Pendiente; const statusClass q.status accepted ? qstatus-responded : q.status rejected ? qstatus-rejected : ; return `div classquote-req-card> div classquote-req-header> div classquote-req-client>Cotización/div> span classquote-req-status ${statusClass}>${statusLabel}/span> /div> div classquote-req-message>${q.message || Sin mensaje}/div> div classquote-req-meta> span>📅 ${date}/span> ${q.price ? `span>💰 $${q.price}/span>` : } /div> /div>`; }).join(); } catch(err) { console.error(Quotes error:, err); el.innerHTML p stylecolor:var(--text-light);>Error cargando cotizaciones/p>; }}// ═══════ UTILIDADES DE CONVERSION ═══════// Scroll al primer profesional visible (para boton flotante WA)window.scrollToFirstPro function() { const firstCard document.querySelector(.pro-card); if (firstCard) { firstCard.scrollIntoView({ behavior: smooth, block: center }); // Destacar el primer card brevemente firstCard.style.boxShadow 0 0 0 3px var(--whatsapp); setTimeout(() > { firstCard.style.boxShadow ; }, 2000); }};// ═══════ INIT ═══════loadAuthState();initMap();loadData();/script>/body>/html>
View on OTX
|
View on ThreatMiner
Please enable JavaScript to view the
comments powered by Disqus.
Data with thanks to
AlienVault OTX
,
VirusTotal
,
Malwr
and
others
. [
Sitemap
]