/* PROCESO — detailed page with timeline & flow animation */ const { useState: useStateP, useEffect: useEffectP, useRef: useRefP } = React; const FlowAnimation = ({ kind }) => { const ref = useRefP(null); useEffectP(() => { const c = ref.current; if (!c) return; const ctx = c.getContext('2d'); let raf, t = 0; const GREEN = '#22c55e', CYAN = '#06b6d4', AMBER = '#f59e0b'; const clamp = (v, a, b) => Math.max(a, Math.min(b, v)); const ease = (x) => x < 0.5 ? 2*x*x : 1 - Math.pow(-2*x+2, 2)/2; const rr = (x, y, ww, hh, rad) => { const k = Math.min(rad, ww/2, hh/2); ctx.beginPath(); ctx.moveTo(x+k, y); ctx.arcTo(x+ww, y, x+ww, y+hh, k); ctx.arcTo(x+ww, y+hh, x, y+hh, k); ctx.arcTo(x, y+hh, x, y, k); ctx.arcTo(x, y, x+ww, y, k); ctx.closePath(); }; const resize = () => { const r = c.getBoundingClientRect(); const dpr = window.devicePixelRatio || 1; c.width = r.width * dpr; c.height = r.height * dpr; ctx.setTransform(1,0,0,1,0,0); ctx.scale(dpr, dpr); }; resize(); const ro = new ResizeObserver(resize); ro.observe(c); const draw = () => { t += 1/60; const r = c.getBoundingClientRect(); const w = r.width, h = r.height; const P = 22; // inner padding ctx.clearRect(0,0,w,h); if (kind === 'discover') { /* DISCOVERY — matriz Impacto / Esfuerzo: las oportunidades se grafican y las 3 mejores (alto impacto, bajo esfuerzo) se rankean 1·2·3. Justo lo que entrega la etapa: "Top 3 priorizadas por impacto/esfuerzo". */ const gx0 = P + 30, gx1 = w - P - 8, gy0 = P + 12, gy1 = h - P - 22; const cyc = 7.5, lt = t % cyc; // "quick wins" zone (alto impacto, bajo esfuerzo) const qzW = (gx1-gx0)*0.42, qzH = (gy1-gy0)*0.5; const scanning = lt > 2 && lt < 4.3; ctx.fillStyle = 'rgba(34,197,94,0.06)'; rr(gx0, gy0, qzW, qzH, 8); ctx.fill(); ctx.setLineDash([4,3]); ctx.lineWidth = 1; ctx.strokeStyle = scanning ? `rgba(34,197,94,${0.4 + ((Math.sin(t*4)+1)/2)*0.4})` : 'rgba(34,197,94,0.22)'; rr(gx0, gy0, qzW, qzH, 8); ctx.stroke(); ctx.setLineDash([]); // axes + arrows ctx.strokeStyle = 'rgba(255,255,255,0.16)'; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.moveTo(gx0, gy0-4); ctx.lineTo(gx0, gy1); ctx.lineTo(gx1, gy1); ctx.stroke(); ctx.fillStyle = 'rgba(255,255,255,0.3)'; ctx.beginPath(); ctx.moveTo(gx0, gy0-8); ctx.lineTo(gx0-3.5, gy0-2); ctx.lineTo(gx0+3.5, gy0-2); ctx.fill(); ctx.beginPath(); ctx.moveTo(gx1+7, gy1); ctx.lineTo(gx1+1, gy1-3.5); ctx.lineTo(gx1+1, gy1+3.5); ctx.fill(); // labels ctx.fillStyle = 'rgba(255,255,255,0.42)'; ctx.font = '600 9px monospace'; ctx.save(); ctx.translate(gx0-15, (gy0+gy1)/2); ctx.rotate(-Math.PI/2); ctx.textAlign = 'center'; ctx.fillText('IMPACTO', 0, 0); ctx.restore(); ctx.textAlign = 'center'; ctx.fillText('ESFUERZO', (gx0+gx1)/2, gy1+15); ctx.textAlign = 'start'; const opp = [ { e:0.15, i:0.83, rank:1 }, { e:0.30, i:0.66, rank:2 }, { e:0.22, i:0.54, rank:3 }, { e:0.55, i:0.80 }, { e:0.72, i:0.58 }, { e:0.83, i:0.33 }, { e:0.50, i:0.30 }, { e:0.66, i:0.17 }, { e:0.42, i:0.44 }, { e:0.87, i:0.71 }, ]; const cxp = (gx0+gx1)/2, cyp = (gy0+gy1)/2; opp.forEach((o, idx) => { const tx = gx0 + o.e*(gx1-gx0), ty = gy1 - o.i*(gy1-gy0); const a = ease(clamp((lt - idx*0.09)/0.7, 0, 1)); const px = cxp + (tx-cxp)*a, py = cyp + (ty-cyp)*a; const isWin = !!o.rank; ctx.globalAlpha = 0.3 + a*0.7; ctx.fillStyle = isWin ? GREEN : 'rgba(255,255,255,0.42)'; ctx.beginPath(); ctx.arc(px, py, isWin ? 5.5 : 4, 0, Math.PI*2); ctx.fill(); ctx.globalAlpha = 1; if (isWin) { const bt = lt - (4.4 + o.rank*0.4); if (bt > 0) { const ba = ease(clamp(bt/0.5, 0, 1)); const pulse = (Math.sin(t*3 + o.rank) + 1)/2; ctx.strokeStyle = `rgba(34,197,94,${0.45*ba + pulse*0.35})`; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(px, py, 9 + pulse*3, 0, Math.PI*2*ba); ctx.stroke(); ctx.fillStyle = GREEN; ctx.beginPath(); ctx.arc(px+10, py-10, 7.5*ba, 0, Math.PI*2); ctx.fill(); if (ba > 0.6) { ctx.fillStyle = '#04140b'; ctx.font = '700 9px monospace'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(String(o.rank), px+10, py-9.5); ctx.textAlign = 'start'; ctx.textBaseline = 'alphabetic'; } } } }); } else if (kind === 'design') { /* DISEÑO — editor tipo Figma: rail de capas + una "pasada de diseño" que convierte el wireframe (gris) en diseño visual (color) de arriba a abajo; luego el cursor hace clic en el CTA (prototipo interactivo) y aparecen los componentes del design system. */ const wx = P, wy = P, ww = w - 2*P, wh = h - 2*P; ctx.fillStyle = 'rgba(255,255,255,0.025)'; ctx.strokeStyle = 'rgba(255,255,255,0.12)'; ctx.lineWidth = 1; rr(wx, wy, ww, wh, 12); ctx.fill(); ctx.stroke(); const barH = 24; ctx.strokeStyle = 'rgba(255,255,255,0.08)'; ctx.beginPath(); ctx.moveTo(wx, wy+barH); ctx.lineTo(wx+ww, wy+barH); ctx.stroke(); ['#ef4444','#f59e0b','#22c55e'].forEach((col,i)=>{ ctx.fillStyle = col; ctx.globalAlpha = 0.6; ctx.beginPath(); ctx.arc(wx+15+i*11, wy+12, 3, 0, Math.PI*2); ctx.fill(); }); ctx.globalAlpha = 1; const cyc = 7, lt = t % cyc; // left rail — layers panel const sbW = 66, sbX = wx, sbY = wy+barH; ctx.strokeStyle = 'rgba(255,255,255,0.07)'; ctx.beginPath(); ctx.moveTo(sbX+sbW, sbY); ctx.lineTo(sbX+sbW, wy+wh); ctx.stroke(); ctx.fillStyle = 'rgba(255,255,255,0.32)'; ctx.font = '600 8px monospace'; ctx.fillText('CAPAS', sbX+11, sbY+15); for (let i=0;i<5;i++){ const ly = sbY+26+i*14; const activeLayer = lt < 2.8 && Math.floor((lt/2.8)*5) === i; ctx.fillStyle = activeLayer ? GREEN : 'rgba(255,255,255,0.35)'; ctx.beginPath(); ctx.arc(sbX+13, ly, 2.4, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = activeLayer ? 'rgba(34,197,94,0.5)' : 'rgba(255,255,255,0.12)'; rr(sbX+21, ly-2.5, 32 - (i%2)*8, 5, 2); ctx.fill(); } // main canvas const mx0 = sbX+sbW+14, my0 = sbY+12; const mw = wx+ww-14-mx0, mh = wy+wh-12-my0-16; const sweepP = ease(clamp(lt/2.8, 0, 1)); const sweepY = my0 + sweepP*mh; const els = [ { y:0.02, h:0.11, w:0.55, t:'title' }, { y:0.18, h:0.30, w:1.00, t:'img' }, { y:0.54, h:0.045, w:0.92, t:'text' }, { y:0.62, h:0.045, w:0.74, t:'text' }, { y:0.70, h:0.045, w:0.58, t:'text' }, { y:0.82, h:0.13, w:0.40, t:'btn' }, ]; const btn = els[5]; const btnX = mx0, btnY = my0 + btn.y*mh, btnW = btn.w*mw, btnH = btn.h*mh; els.forEach(e => { const ex = mx0, ey = my0 + e.y*mh, ew = e.w*mw, eh = e.h*mh; const designed = ey + eh/2 < sweepY; if (e.t === 'img') { if (designed) { const g = ctx.createLinearGradient(ex, ey, ex+ew, ey+eh); g.addColorStop(0, 'rgba(6,182,212,0.28)'); g.addColorStop(1, 'rgba(34,197,94,0.18)'); ctx.fillStyle = g; } else ctx.fillStyle = 'rgba(255,255,255,0.05)'; rr(ex, ey, ew, eh, 8); ctx.fill(); ctx.strokeStyle = designed ? 'rgba(6,182,212,0.5)' : 'rgba(255,255,255,0.14)'; ctx.lineWidth = 1; rr(ex, ey, ew, eh, 8); ctx.stroke(); if (!designed) { ctx.strokeStyle = 'rgba(255,255,255,0.12)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(ex, ey); ctx.lineTo(ex+ew, ey+eh); ctx.moveTo(ex+ew, ey); ctx.lineTo(ex, ey+eh); ctx.stroke(); } } else if (e.t === 'btn') { const clicked = lt > 4.2 && lt < 5.4; ctx.fillStyle = designed ? (clicked ? '#16a34a' : GREEN) : 'rgba(255,255,255,0.08)'; rr(ex, ey, ew, eh, 6); ctx.fill(); if (!designed) { ctx.strokeStyle = 'rgba(255,255,255,0.16)'; ctx.lineWidth = 1; rr(ex, ey, ew, eh, 6); ctx.stroke(); } } else if (e.t === 'title') { ctx.fillStyle = designed ? 'rgba(74,222,128,0.9)' : 'rgba(255,255,255,0.16)'; rr(ex, ey, ew, eh, 3); ctx.fill(); } else { ctx.fillStyle = designed ? 'rgba(255,255,255,0.5)' : 'rgba(255,255,255,0.12)'; rr(ex, ey, ew, eh, 2.5); ctx.fill(); } }); // design sweep glow line if (sweepP < 0.999) { const g = ctx.createLinearGradient(0, sweepY-10, 0, sweepY+10); g.addColorStop(0, 'rgba(34,197,94,0)'); g.addColorStop(0.5, 'rgba(34,197,94,0.22)'); g.addColorStop(1, 'rgba(34,197,94,0)'); ctx.fillStyle = g; ctx.fillRect(mx0, sweepY-10, mw, 20); ctx.strokeStyle = 'rgba(74,222,128,0.8)'; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.moveTo(mx0, sweepY); ctx.lineTo(mx0+mw, sweepY); ctx.stroke(); } // design-system component chips (bottom strip) const chY = wy+wh-13; ['#22c55e','#06b6d4','rgba(255,255,255,0.4)','#f59e0b'].forEach((col,i)=>{ const cha = ease(clamp((lt - 3 - i*0.2)/0.5, 0, 1)); if (cha <= 0) return; ctx.globalAlpha = cha; ctx.fillStyle = col; rr(mx0 + i*28, chY-6, 20, 9, 3); ctx.fill(); ctx.globalAlpha = 1; }); // click ripple + cursor (prototipo interactivo) const bcx = btnX+btnW/2, bcy = btnY+btnH/2; if (lt > 4.2 && lt < 5.4) { const rp = (lt - 4.2)/1.2; ctx.strokeStyle = `rgba(34,197,94,${0.6*(1-rp)})`; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(bcx, bcy, 6 + rp*30, 0, Math.PI*2); ctx.stroke(); } const cp = ease(clamp((lt - 3)/1.3, 0, 1)); const sX = mx0 + mw*0.9, sY = my0 + mh*0.1; const cx = sX + (bcx - sX)*cp, cy = sY + (bcy - sY)*cp; ctx.fillStyle = '#fff'; ctx.strokeStyle = 'rgba(0,0,0,0.4)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(cx, cy); ctx.lineTo(cx, cy+15); ctx.lineTo(cx+4, cy+11); ctx.lineTo(cx+7, cy+17); ctx.lineTo(cx+9, cy+16); ctx.lineTo(cx+6, cy+10); ctx.lineTo(cx+11, cy+10); ctx.closePath(); ctx.fill(); ctx.stroke(); } else if (kind === 'build') { /* DESARROLLO — tablero kanban de sprint: las tarjetas fluyen de Backlog → En curso → Listo. En "En curso" muestran barra de progreso; al llegar a "Listo" se marcan con check. */ const labels = ['BACKLOG', 'EN CURSO', 'LISTO']; const usableW = w - 2*P, zoneW = usableW/3; // column zones + headers for (let i = 0; i < 3; i++) { const zx = P + i*zoneW; ctx.fillStyle = i === 2 ? 'rgba(34,197,94,0.055)' : 'rgba(255,255,255,0.025)'; rr(zx + 3, P + 2, zoneW - 6, h - 2*P - 4, 10); ctx.fill(); ctx.fillStyle = i === 2 ? 'rgba(74,222,128,0.85)' : 'rgba(255,255,255,0.38)'; ctx.font = '600 10px monospace'; ctx.textAlign = 'center'; ctx.fillText(labels[i], zx + zoneW/2, P + 17); } ctx.textAlign = 'start'; const rows = 4; const cw = Math.min(zoneW - 26, 148), ch = 38; const areaTop = P + 28, areaH = h - 2*P - 34; const rowH = areaH / rows; for (let rI = 0; rI < rows; rI++) { const phase = (t*0.26 + rI*0.72) % 3; // 0..3 across the 3 columns const zone = Math.floor(phase); const cx = P + (phase/3)*(usableW - cw); const cy = areaTop + rI*rowH + (rowH - ch)/2; const inProg = zone === 1, done = zone === 2; const tagCol = done ? GREEN : inProg ? CYAN : AMBER; // card ctx.fillStyle = done ? 'rgba(34,197,94,0.10)' : 'rgba(255,255,255,0.05)'; ctx.strokeStyle = done ? 'rgba(34,197,94,0.5)' : inProg ? 'rgba(6,182,212,0.4)' : 'rgba(255,255,255,0.14)'; ctx.lineWidth = 1; rr(cx, cy, cw, ch, 8); ctx.fill(); ctx.stroke(); // colored tag bar (left accent) ctx.fillStyle = tagCol; rr(cx, cy, 3, ch, 1.5); ctx.fill(); // skeleton lines ctx.fillStyle = 'rgba(255,255,255,0.28)'; rr(cx+12, cy+10, cw*0.5, 4, 2); ctx.fill(); ctx.fillStyle = 'rgba(255,255,255,0.14)'; rr(cx+12, cy+19, cw*0.66, 3, 1.5); ctx.fill(); // progress bar while en curso if (inProg) { const pr = phase - 1; ctx.fillStyle = 'rgba(255,255,255,0.10)'; rr(cx+12, cy+ch-9, cw-24, 3, 1.5); ctx.fill(); const gp = ctx.createLinearGradient(cx, 0, cx+cw, 0); gp.addColorStop(0, GREEN); gp.addColorStop(1, CYAN); ctx.fillStyle = gp; rr(cx+12, cy+ch-9, (cw-24)*pr, 3, 1.5); ctx.fill(); } // check when done if (done) { const mx = cx + cw - 13, my = cy + ch/2; ctx.fillStyle = 'rgba(34,197,94,0.18)'; ctx.beginPath(); ctx.arc(mx, my, 7, 0, Math.PI*2); ctx.fill(); ctx.strokeStyle = GREEN; ctx.lineWidth = 1.6; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(mx-3, my); ctx.lineTo(mx-0.5, my+2.5); ctx.lineTo(mx+3.5, my-3); ctx.stroke(); ctx.lineCap = 'butt'; } } } else if (kind === 'launch') { /* LANZAMIENTO — despliegue por etapas: arranca con el equipo piloto y se expande a toda la organización en oleadas. La etiqueta de fase va de Piloto → Ampliando → En producción, y abajo corre el monitoreo en vivo (latido) de las primeras semanas. */ const cyc = 8, lt = t % cyc; const cols = 18, rows = 4; const gx0 = P + 6, gx1 = w - P - 6; const gridTop = P + 32, gridH = (h - 2*P) * 0.46; const gridBot = gridTop + gridH; const sx = (gx1 - gx0) / (cols - 1); const sy = rows > 1 ? gridH / (rows - 1) : 0; // activation front expands from the piloto cluster (col0,row0) let maxD = 0; for (let r=0;rmaxD) maxD=d; } const front = ease(clamp(lt/5, 0, 1)) * (maxD + 0.8); let activated = 0; const total = cols*rows; for (let r=0;r d; if (on) activated++; if (on && (front - d) < 0.9) { const pr = 1-(front-d)/0.9; ctx.fillStyle = `rgba(34,197,94,${0.28*pr})`; ctx.beginPath(); ctx.arc(px, py, 3.4+pr*7, 0, Math.PI*2); ctx.fill(); } ctx.fillStyle = on ? GREEN : 'rgba(255,255,255,0.14)'; ctx.beginPath(); ctx.arc(px, py, 3.2, 0, Math.PI*2); ctx.fill(); } } // piloto cluster outline (2×2) + label ctx.strokeStyle = 'rgba(6,182,212,0.55)'; ctx.setLineDash([3,2]); ctx.lineWidth = 1; rr(gx0-5, gridTop-5, sx+10, sy+10, 5); ctx.stroke(); ctx.setLineDash([]); ctx.fillStyle = 'rgba(34,211,238,0.85)'; ctx.font = '600 8px monospace'; ctx.fillText('PILOTO', gx0-5, gridTop-9); // stage label + progress % const pct = activated/total; const stage = pct < 0.16 ? 'PILOTO · 1 EQUIPO' : pct < 0.8 ? 'AMPLIANDO A LA EMPRESA' : 'EN PRODUCCIÓN'; ctx.fillStyle = 'rgba(255,255,255,0.6)'; ctx.font = '600 11px monospace'; ctx.textAlign = 'start'; ctx.fillText(stage, gx0, P+12); ctx.fillStyle = GREEN; ctx.textAlign = 'end'; ctx.fillText(Math.round(pct*100)+'%', gx1, P+12); ctx.textAlign = 'start'; // monitoring heartbeat (monitoreo intensivo) const mLabelY = gridBot + 20, monY = gridBot + 44; ctx.fillStyle = 'rgba(255,255,255,0.4)'; ctx.font = '600 8px monospace'; ctx.fillText('MONITOREO EN VIVO', gx0, mLabelY); const pulse = (Math.sin(t*4)+1)/2; ctx.fillStyle = `rgba(34,197,94,${0.4+pulse*0.5})`; ctx.beginPath(); ctx.arc(gx0+100, mLabelY-3, 2.4+pulse*1.6, 0, Math.PI*2); ctx.fill(); ctx.strokeStyle = 'rgba(255,255,255,0.07)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(gx0, monY); ctx.lineTo(gx1, monY); ctx.stroke(); const gm = ctx.createLinearGradient(gx0, 0, gx1, 0); gm.addColorStop(0, GREEN); gm.addColorStop(1, CYAN); ctx.strokeStyle = gm; ctx.lineWidth = 2; ctx.lineJoin = 'round'; ctx.beginPath(); for (let x = gx0; x <= gx1; x++) { const s = (x - gx0) + t*70; const beat = s % 130; const spike = Math.exp(-Math.pow(beat-65, 2)/28); const y = monY - spike*(11 + pulse*3) + Math.sin((x-gx0)*0.14)*1.4; if (x === gx0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } ctx.stroke(); } else { /* EVOLUCIÓN — roadmap que se extiende: la línea de tiempo crece y va revelando nuevos módulos uno a uno, mes a mes. Un tip luminoso lidera el avance; cada módulo aparece con un pop + su etiqueta. */ const baseY = h/2 + 6; const x0 = P + 4, x1 = w - P - 4; // full faint track + month ticks ctx.strokeStyle = 'rgba(255,255,255,0.08)'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(x0, baseY); ctx.lineTo(x1, baseY); ctx.stroke(); for (let m = 0; m <= 6; m++) { const tx = x0 + (m/6)*(x1-x0); ctx.strokeStyle = 'rgba(255,255,255,0.10)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(tx, baseY-3); ctx.lineTo(tx, baseY+3); ctx.stroke(); } const g = clamp((t*0.14) % 1.28, 0, 1); // grow, then brief pause const tipX = x0 + g*(x1 - x0); // grown portion const gl = ctx.createLinearGradient(x0, 0, x1, 0); gl.addColorStop(0, GREEN); gl.addColorStop(1, CYAN); ctx.strokeStyle = gl; ctx.lineWidth = 3.5; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(x0, baseY); ctx.lineTo(tipX, baseY); ctx.stroke(); ctx.lineCap = 'butt'; const mods = [ { fx: 0.13, up: true, label: 'SLA' }, { fx: 0.31, up: false, label: 'Roadmap' }, { fx: 0.50, up: true, label: 'Módulo' }, { fx: 0.69, up: false, label: 'Integración' }, { fx: 0.87, up: true, label: 'Reporte' }, ]; const cardW = Math.min(104, (x1-x0)/5.4), cardH = 42, gapV = 15; mods.forEach((m) => { const mx = x0 + m.fx*(x1-x0); const revealed = tipX >= mx; if (!revealed) { ctx.fillStyle = 'rgba(255,255,255,0.16)'; ctx.beginPath(); ctx.arc(mx, baseY, 3, 0, Math.PI*2); ctx.fill(); return; } const ap = ease(clamp((tipX - mx)/26, 0, 1)); const dir = m.up ? -1 : 1; const off = (1 - ap) * 10 * dir; const connEnd = baseY + dir*gapV + off; const cardY = m.up ? connEnd - cardH : connEnd; let cardX = mx - cardW/2; cardX = clamp(cardX, x0, x1 - cardW); ctx.globalAlpha = ap; // connector ctx.strokeStyle = 'rgba(34,197,94,0.45)'; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.moveTo(mx, baseY); ctx.lineTo(mx, connEnd); ctx.stroke(); // card ctx.fillStyle = 'rgba(20,30,28,0.92)'; ctx.strokeStyle = 'rgba(34,197,94,0.4)'; ctx.lineWidth = 1; rr(cardX, cardY, cardW, cardH, 8); ctx.fill(); ctx.stroke(); // "+" badge const bx2 = cardX + 12, by2 = cardY + 13; ctx.strokeStyle = GREEN; ctx.lineWidth = 1.6; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(bx2-3, by2); ctx.lineTo(bx2+3, by2); ctx.moveTo(bx2, by2-3); ctx.lineTo(bx2, by2+3); ctx.stroke(); ctx.lineCap = 'butt'; // label + subline ctx.fillStyle = 'rgba(255,255,255,0.9)'; ctx.font = '600 10px monospace'; ctx.fillText(m.label, cardX + 22, cardY + 16); ctx.fillStyle = 'rgba(255,255,255,0.14)'; rr(cardX+12, cardY+26, cardW-24, 3, 1.5); ctx.fill(); ctx.fillStyle = 'rgba(255,255,255,0.10)'; rr(cardX+12, cardY+33, cardW*0.5, 3, 1.5); ctx.fill(); ctx.globalAlpha = 1; // node on baseline const pulse = (Math.sin(t*2.2 + m.fx*10) + 1)/2; ctx.fillStyle = `rgba(34,197,94,${0.18 + pulse*0.22*ap})`; ctx.beginPath(); ctx.arc(mx, baseY, 4 + pulse*4*ap, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = GREEN; ctx.beginPath(); ctx.arc(mx, baseY, 3.5, 0, Math.PI*2); ctx.fill(); }); // leading glow tip while growing if (g < 0.999) { const tp = (Math.sin(t*4) + 1)/2; ctx.fillStyle = 'rgba(6,182,212,0.25)'; ctx.beginPath(); ctx.arc(tipX, baseY, 6 + tp*4, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = '#fff'; ctx.shadowColor = CYAN; ctx.shadowBlur = 12; ctx.beginPath(); ctx.arc(tipX, baseY, 3.5, 0, Math.PI*2); ctx.fill(); ctx.shadowBlur = 0; } } raf = requestAnimationFrame(draw); }; draw(); return () => { cancelAnimationFrame(raf); ro.disconnect(); }; }, [kind]); return ; }; const ProcHero = () => (
Inicio/Proceso

Un proceso transparente, diseñado para que duermas tranquilo.

Trabajar con un equipo de software no debería sentirse como una caja negra. Estas son las 5 etapas por las que vas a pasar — qué entregamos, cuánto tarda, qué decisiones tomas tú.

); const stages = [ { n: '01', id: 'discovery', t: 'Discovery', h: 'Entender tu negocio antes de tocar código', p: 'Una semana intensiva: mapeamos procesos, entrevistamos a tu equipo, identificamos los 3 cuellos de botella que más dinero te están costando. Sales con claridad sobre qué construir primero — y qué no.', deliv: [ { t: 'Mapa de operación', d: 'Diagrama visual de tus flujos actuales' }, { t: 'Top 3 oportunidades', d: 'Priorizadas por impacto/esfuerzo' }, { t: 'Especificación funcional', d: 'Documento vivo en Notion compartido' }, { t: 'Estimación honesta', d: 'Rango de inversión + cronograma' }, ], dur: '1–2 semanas', kind: 'discover', }, { n: '02', id: 'design', t: 'Diseño', h: 'Prototipos clickeables antes de cualquier línea de código', p: 'Diseñamos la experiencia completa en Figma. Tú haces clic, lo pruebas, lo destrozas, lo cambias. Iteramos hasta que sientas que el sistema ya está vivo. Solo entonces pasamos a desarrollo.', deliv: [ { t: 'Wireframes de baja fidelidad', d: 'Para validar la estructura' }, { t: 'Diseño visual', d: 'Tu marca, tu identidad, tu look' }, { t: 'Prototipo interactivo', d: 'Clickeable como si fuera real' }, { t: 'Design system', d: 'Componentes reusables documentados' }, ], dur: '2–3 semanas', kind: 'design', }, { n: '03', id: 'build', t: 'Desarrollo', h: 'Sprints de 2 semanas con demos en vivo', p: 'Cada 14 días te mostramos lo que hicimos en una llamada de 30 minutos. Pruebas, comentas, priorizamos. Tu sistema crece a la vista — sin sorpresas el día del lanzamiento, sin "ya casi".', deliv: [ { t: 'Demo bisemanal en vivo', d: 'Sin slides — el producto real' }, { t: 'Acceso a staging', d: 'Pruébalo cuando quieras' }, { t: 'Backlog priorizado', d: 'Tu equipo decide qué sigue' }, { t: 'Tests automatizados', d: 'Cobertura en la lógica central' }, ], dur: '6–16 semanas', kind: 'build', }, { n: '04', id: 'launch', t: 'Lanzamiento', h: 'De staging a producción sin drama', p: 'Hacemos el cutover por etapas: piloto con un equipo, luego ampliamos. Capacitamos a tu gente con sesiones grabadas. Monitoreamos cada métrica las primeras 2 semanas para resolver fricción antes de que la sientas.', deliv: [ { t: 'Piloto controlado', d: 'Empieza con 1 equipo o sucursal' }, { t: 'Capacitación grabada', d: 'Para nuevos usuarios futuros' }, { t: 'Migración de datos', d: 'De tu sistema actual sin pérdidas' }, { t: 'Monitoreo intensivo', d: 'Las primeras 2 semanas en vivo' }, ], dur: '1–2 semanas', kind: 'launch', }, { n: '05', id: 'evolve', t: 'Evolución', h: 'Tu sistema crece contigo, no se queda en el día 1', p: 'Después del lanzamiento seguimos contigo: nuevos módulos cuando los necesites, integraciones según vayas adoptando herramientas, mejoras basadas en uso real. Slack directo con los devs, no tickets fríos.', deliv: [ { t: 'Soporte por escrito', d: 'Tiempos de respuesta acordados contigo' }, { t: 'Roadmap mensual', d: 'Tú priorizas, nosotros ejecutamos' }, { t: 'Reporte mensual', d: 'Qué cambió, qué métricas mejoraron' }, { t: 'Slack directo', d: 'Con los developers que conocen tu sistema' }, ], dur: 'Continua', kind: 'evolve', }, ]; const Timeline = () => { const [active, setActive] = useStateP(0); const [direction, setDirection] = useStateP(0); const tabRefs = useRefP([]); const nextStage = () => { if (active < stages.length - 1) { setDirection(1); setActive(active + 1); } }; const prevStage = () => { if (active > 0) { setDirection(-1); setActive(active - 1); } }; const currentStage = stages[active]; const selectStage = (index, focus = false) => { const next = (index + stages.length) % stages.length; setDirection(next > active ? 1 : -1); setActive(next); requestAnimationFrame(() => { const tab = tabRefs.current[next]; if (!tab) return; if (focus) tab.focus(); if (window.innerWidth <= 700) tab.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); }); }; const handleTabKey = (e, index) => { if (e.key === 'ArrowRight' || e.key === 'ArrowDown') { e.preventDefault(); selectStage(index + 1, true); } if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') { e.preventDefault(); selectStage(index - 1, true); } if (e.key === 'Home') { e.preventDefault(); selectStage(0, true); } if (e.key === 'End') { e.preventDefault(); selectStage(stages.length - 1, true); } }; return (
Nuestro proceso

5 etapas claras. Cero sorpresas.

{/* Tabs */}
{stages.map((s, i) => ( ))}
{/* Content Window */}
{currentStage.n} {currentStage.dur}

{currentStage.h}

{currentStage.p}

{currentStage.deliv.map((d, i) => (
{d.t}{d.d}
))}
{/* Navigation */}
{stages.map((_, i) => ( ))}
); }; const ProcWhy = () => (
Por qué este proceso

Está diseñado para que tu inversión sea segura.

Iteración corta

Demos cada 2 semanas — si algo no va bien, lo descubres temprano, no al final.

Tu propiedad

El código vive en tu repositorio. Si quieres cambiar de equipo, no hay rehén tecnológico.

Salida limpia

No firmas contratos eternos. Cada fase tiene un punto natural para parar si algo no encaja.

Sin sobre-ingeniería

Construimos lo que necesitas hoy y lo que vas a necesitar mañana — no lo que se ve impresionante.

); const ProcApp = () => ( <> ); ReactDOM.createRoot(document.getElementById('root')).render();