{"id":1455,"date":"2026-10-05T10:50:25","date_gmt":"2026-10-05T10:50:25","guid":{"rendered":"https:\/\/javierladino.com\/es\/?post_type=rara-portfolio&#038;p=1455"},"modified":"2026-10-05T12:19:56","modified_gmt":"2026-10-05T12:19:56","slug":"rover-mars-el-pipeline-de-datos-de-la-nasa-reconstruido-de-punta-a-punta","status":"publish","type":"rara-portfolio","link":"https:\/\/javierladino.com\/es\/portfolio\/rover-mars-el-pipeline-de-datos-de-la-nasa-reconstruido-de-punta-a-punta\/","title":{"rendered":"Rover Mars \u2014 El pipeline de datos de la NASA, reconstruido de punta a punta"},"content":{"rendered":"\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"450\" src=\"https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/demo.gif\" alt=\"\" class=\"wp-image-1456\" style=\"width:840px;height:auto\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><br>Repliqu\u00e9, de punta a punta, el sistema que la NASA usa para recibir y procesar las fotos y los datos ambientales del rover Perseverance en Marte. No es un notebook de ejemplo: es un pipeline de datos real, con el protocolo espacial aut\u00e9ntico, corriendo con datos generados en vivo.<\/p>\n\n\n\n<div id=\"rover-flux\">\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin>\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Rajdhani:wght@500;600;700&#038;family=Share+Tech+Mono&#038;display=swap\" rel=\"stylesheet\">\n<style>\n    #rover-flux {\n        --bg-dark: #050505;\n        --bg-panel: #0e0e16;\n        --neon-cyan: #00f3ff;\n        --neon-pink: #ff0099;\n        --neon-green: #39ff14;\n        --neon-yellow: #f3ff00;\n        --mars: #ff6b6b;\n        --space: #a29bfe;\n        --text-main: #e0e0e0;\n        --text-dim: #777;\n        --line-color: #333;\n        container-type: inline-size;\n        color-scheme: dark;\n        box-sizing: border-box;\n        width: 100%;\n        margin: 0;\n        padding: 40px 16px;\n        border-radius: 6px;\n        background-color: var(--bg-dark);\n        background-image:\n            radial-gradient(circle at 10% 20%, rgba(0, 243, 255, 0.06) 0%, transparent 20%),\n            radial-gradient(circle at 90% 80%, rgba(255, 0, 153, 0.06) 0%, transparent 20%);\n        color: var(--text-main);\n        font-family: 'Rajdhani', sans-serif;\n        line-height: 1.4;\n        display: flex;\n        flex-direction: column;\n        align-items: center;\n    }\n\n    #rover-flux *,\n    #rover-flux *::before,\n    #rover-flux *::after { box-sizing: border-box; }\n\n    #rover-flux [hidden] { display: none !important; }\n\n    #rover-flux .header { position: relative; width: 100%; max-width: 1400px; text-align: center; }\n\n    #rover-flux h1 {\n        font-family: 'Share Tech Mono', monospace;\n        color: var(--neon-cyan);\n        text-transform: uppercase;\n        letter-spacing: 4px;\n        margin: 0 0 10px;\n        text-shadow: 0 0 15px rgba(0, 243, 255, 0.5);\n        font-size: clamp(1.2rem, 4vw, 2rem);\n        line-height: 1.2;\n    }\n\n    #rover-flux .subtitle {\n        color: var(--neon-pink);\n        font-size: 1rem;\n        margin-bottom: 20px;\n        font-family: 'Share Tech Mono', monospace;\n    }\n\n    #rover-flux .latency-indicator {\n        display: inline-flex;\n        align-items: center;\n        gap: 6px;\n        color: var(--neon-pink);\n        font-family: 'Share Tech Mono', monospace;\n        font-size: 0.8rem;\n        letter-spacing: 1px;\n        border: 1px solid var(--neon-pink);\n        border-radius: 3px;\n        padding: 4px 10px;\n        margin-bottom: 24px;\n    }\n    #rover-flux .latency-indicator svg { width: 14px; height: 14px; }\n\n    #rover-flux .pipeline-toggle {\n        display: inline-flex;\n        gap: 2px;\n        margin-bottom: 20px;\n        border: 1px solid var(--line-color);\n        border-radius: 3px;\n        padding: 3px;\n        background: var(--bg-panel);\n    }\n    #rover-flux .toggle-btn {\n        font-family: 'Share Tech Mono', monospace;\n        font-size: 0.8rem;\n        letter-spacing: 2px;\n        text-transform: none;\n        line-height: 1.2;\n        background: transparent;\n        color: var(--text-dim);\n        border: none;\n        border-radius: 2px;\n        padding: 7px 16px;\n        cursor: pointer;\n        transition: all 0.25s ease;\n    }\n    #rover-flux .toggle-btn:hover { color: var(--text-main); }\n    #rover-flux .toggle-btn.active {\n        background: rgba(0, 243, 255, 0.1);\n        color: var(--neon-cyan);\n        box-shadow: inset 0 0 0 1px var(--neon-cyan), 0 0 10px rgba(0, 243, 255, 0.3);\n    }\n    #rover-flux .toggle-btn:focus-visible { outline: 2px solid var(--neon-cyan); outline-offset: 2px; }\n\n    #rover-flux .gov-ticker {\n        width: 100%;\n        max-width: 1400px;\n        overflow: hidden;\n        border-top: 1px solid var(--line-color);\n        border-bottom: 1px solid var(--line-color);\n        margin-bottom: 30px;\n        padding: 9px 0;\n    }\n    #rover-flux .gov-ticker-track {\n        display: flex;\n        width: max-content;\n        animation: rf-ticker 38s linear infinite;\n    }\n    #rover-flux .gov-ticker-track span {\n        font-family: 'Share Tech Mono', monospace;\n        font-size: 0.7rem;\n        letter-spacing: 2px;\n        color: var(--neon-yellow);\n        white-space: nowrap;\n        padding-right: 48px;\n    }\n    @keyframes rf-ticker { from { transform: translateX(0); } to { transform: translateX(-50%); } }\n\n    #rover-flux .map-container {\n        display: grid;\n        grid-template-columns: repeat(6, 1fr);\n        gap: 20px;\n        width: 100%;\n        max-width: 1400px;\n        position: relative;\n    }\n\n    #rover-flux .connections {\n        position: absolute;\n        top: 50px;\n        left: 0;\n        width: 100%;\n        height: 100%;\n        pointer-events: none;\n        z-index: 0;\n    }\n    #rover-flux .connection-line {\n        stroke: var(--line-color);\n        stroke-width: 2;\n        fill: none;\n        stroke-dasharray: 10;\n        animation: rf-flow 30s linear infinite;\n    }\n    #rover-flux .connection-active {\n        stroke: var(--neon-cyan);\n        stroke-width: 2;\n        stroke-dasharray: 5 5;\n        animation: rf-flow-fast 2s linear infinite;\n        opacity: 0.6;\n    }\n    @keyframes rf-flow { to { stroke-dashoffset: -1000; } }\n    @keyframes rf-flow-fast { to { stroke-dashoffset: -50; } }\n    @keyframes rf-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }\n\n    #rover-flux .node {\n        background: var(--bg-panel);\n        border: 1px solid #333;\n        border-radius: 4px;\n        padding: 20px;\n        position: relative;\n        z-index: 1;\n        transition: all 0.3s ease;\n        display: flex;\n        flex-direction: column;\n        min-height: 300px;\n        min-width: 0;\n    }\n    #rover-flux .node:hover {\n        transform: translateY(-5px);\n        box-shadow: 0 10px 30px rgba(0, 243, 255, 0.15);\n        border-color: var(--neon-cyan);\n    }\n\n    #rover-flux .node-header {\n        display: flex;\n        align-items: center;\n        gap: 10px;\n        margin-bottom: 15px;\n        padding-bottom: 10px;\n        border-bottom: 1px solid #333;\n    }\n    #rover-flux .node:hover .node-header { border-bottom-color: var(--neon-cyan); }\n\n    #rover-flux .icon-box {\n        width: 38px;\n        height: 38px;\n        flex: none;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        background: rgba(255, 255, 255, 0.05);\n    }\n    #rover-flux .icon-box svg { width: 20px; height: 20px; }\n\n    #rover-flux .mars-color { color: var(--mars); border: 1px solid var(--mars); box-shadow: 0 0 5px var(--mars); }\n    #rover-flux .space-color { color: var(--space); border: 1px solid var(--space); box-shadow: 0 0 5px var(--space); }\n    #rover-flux .earth-color { color: var(--neon-cyan); border: 1px solid var(--neon-cyan); box-shadow: 0 0 5px var(--neon-cyan); }\n    #rover-flux .process-color { color: var(--neon-pink); border: 1px solid var(--neon-pink); box-shadow: 0 0 5px var(--neon-pink); }\n    #rover-flux .viz-color { color: var(--neon-green); border: 1px solid var(--neon-green); box-shadow: 0 0 5px var(--neon-green); }\n\n    #rover-flux .node-title { font-weight: 700; text-transform: uppercase; font-size: 1.05rem; color: #fff; line-height: 1.2; }\n\n    #rover-flux .data-list {\n        font-family: 'Share Tech Mono', monospace;\n        font-size: 0.82rem;\n        color: var(--text-main);\n        margin: 0;\n        padding: 0;\n        list-style: none;\n    }\n    #rover-flux .data-list li {\n        margin: 0 0 8px;\n        padding: 0;\n        display: flex;\n        justify-content: space-between;\n        gap: 8px;\n    }\n    #rover-flux .data-tag {\n        flex: none;\n        font-size: 0.68rem;\n        padding: 2px 6px;\n        background: rgba(255, 255, 255, 0.1);\n        border-radius: 2px;\n        white-space: nowrap;\n    }\n\n    #rover-flux .tech-stack {\n        margin-top: auto;\n        font-size: 0.78rem;\n        color: var(--text-dim);\n        border-top: 1px dashed #333;\n        padding-top: 10px;\n    }\n    #rover-flux .tech-stack span { color: var(--neon-yellow); }\n\n    #rover-flux .status-row {\n        margin-top: 10px;\n        display: flex;\n        align-items: center;\n        gap: 6px;\n        font-size: 0.68rem;\n        color: var(--neon-green);\n        letter-spacing: 1px;\n    }\n    #rover-flux .status-dot {\n        width: 7px;\n        height: 7px;\n        border-radius: 50%;\n        background: var(--neon-green);\n        box-shadow: 0 0 6px var(--neon-green);\n        animation: rf-pulse 1.8s ease-in-out infinite;\n    }\n\n    #rover-flux .zone-labels {\n        display: grid;\n        grid-template-columns: repeat(6, 1fr);\n        gap: 20px;\n        width: 100%;\n        max-width: 1400px;\n        margin-bottom: 20px;\n        text-align: center;\n        font-family: 'Share Tech Mono', monospace;\n        color: #777;\n        text-transform: uppercase;\n        font-size: 0.8rem;\n        letter-spacing: 2px;\n    }\n\n    #rover-flux .node:hover .node-title {\n        animation: rf-glitch 0.3s cubic-bezier(.25, .46, .45, .94) both infinite;\n        color: var(--neon-cyan);\n    }\n    @keyframes rf-glitch {\n        0% { transform: translate(0) }\n        20% { transform: translate(-2px, 2px) }\n        40% { transform: translate(-2px, -2px) }\n        60% { transform: translate(2px, 2px) }\n        80% { transform: translate(2px, -2px) }\n        100% { transform: translate(0) }\n    }\n\n    #rover-flux .footer-note {\n        margin-top: 50px;\n        color: #777;\n        font-family: 'Share Tech Mono', monospace;\n        font-size: 0.75rem;\n        text-align: center;\n    }\n\n    @container (max-width: 900px) {\n        #rover-flux .map-container,\n        #rover-flux .zone-labels { grid-template-columns: repeat(3, 1fr); }\n        #rover-flux .connections { display: none; }\n    }\n    @container (max-width: 520px) {\n        #rover-flux .map-container,\n        #rover-flux .zone-labels { grid-template-columns: 1fr; }\n        #rover-flux .zone-labels > div { display: none; }\n    }\n\n    @media (prefers-reduced-motion: reduce) {\n        #rover-flux .gov-ticker-track,\n        #rover-flux .connection-line,\n        #rover-flux .connection-active,\n        #rover-flux .status-dot,\n        #rover-flux .node:hover .node-title { animation: none !important; }\n    }\n<\/style>\n\n<div class=\"header\">\n    <h1>Cartographie du Flux de Donn\u00e9es<\/h1>\n    <div class=\"subtitle\">ROVER MARS \/\/ PIPELINE R\u00c9EL \u2014 SIMULATION DE BOUT EN BOUT<\/div>\n    <div class=\"latency-indicator\">\n        <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"8.5\"\/><path d=\"M12 7.5V12l3 2\"\/><\/svg>\n        LATENCE SIMUL\u00c9E : 3\u201322 MIN\n    <\/div>\n    <div class=\"pipeline-toggle\" role=\"group\" aria-label=\"Choix du pipeline affich\u00e9\">\n        <button type=\"button\" class=\"toggle-btn active\" data-pipeline=\"mcz\">MASTCAM-Z<\/button>\n        <button type=\"button\" class=\"toggle-btn\" data-pipeline=\"meda\">MEDA<\/button>\n    <\/div>\n<\/div>\n\n<div class=\"gov-ticker\" aria-hidden=\"true\">\n    <div class=\"gov-ticker-track\">\n        <span>GOUVERNANCE : SPEC-DRIVEN DEVELOPMENT \u00b7 CONSTITUTION V1.0 \u00b7 8 PRINCIPES NON N\u00c9GOCIABLES \u00b7 FID\u00c9LIT\u00c9 DU DOMAINE \u00b7 CONTRAT MEDALLION \u00b7 IDEMPOTENCE &amp; D\u00c9TERMINISME \u00b7 V\u00c9RIFICATION R\u00c9ELLE (PYTEST + RUFF) \u00b7 Z\u00c9RO SECRET EN DUR \u00b7 CO\u00dbT AWS MA\u00ceTRIS\u00c9 \u00b7 ADR PAR D\u00c9CISION \u00b7 TRANSFORMATION D\u00c9CLARATIVE (DBT)&nbsp;&nbsp;&nbsp;&nbsp;<\/span>\n        <span>GOUVERNANCE : SPEC-DRIVEN DEVELOPMENT \u00b7 CONSTITUTION V1.0 \u00b7 8 PRINCIPES NON N\u00c9GOCIABLES \u00b7 FID\u00c9LIT\u00c9 DU DOMAINE \u00b7 CONTRAT MEDALLION \u00b7 IDEMPOTENCE &amp; D\u00c9TERMINISME \u00b7 V\u00c9RIFICATION R\u00c9ELLE (PYTEST + RUFF) \u00b7 Z\u00c9RO SECRET EN DUR \u00b7 CO\u00dbT AWS MA\u00ceTRIS\u00c9 \u00b7 ADR PAR D\u00c9CISION \u00b7 TRANSFORMATION D\u00c9CLARATIVE (DBT)&nbsp;&nbsp;&nbsp;&nbsp;<\/span>\n    <\/div>\n<\/div>\n\n<div class=\"zone-labels\">\n    <div>Zone 1 : Origine<\/div>\n    <div>Zone 2 : Encodage<\/div>\n    <div>Zone 3 : Relais<\/div>\n    <div>Zone 4 : R\u00e9ception<\/div>\n    <div>Zone 5 : Traitement<\/div>\n    <div>Zone 6 : Visualisation<\/div>\n<\/div>\n\n<div class=\"map-container\">\n\n    <svg class=\"connections\" aria-hidden=\"true\">\n        <path d=\"M 100 100 L 250 100\" class=\"connection-active\" \/>\n        <path d=\"M 300 100 L 450 100\" class=\"connection-active\" \/>\n        <path d=\"M 500 100 L 650 100\" class=\"connection-active\" \/>\n        <path d=\"M 700 100 L 850 100\" class=\"connection-line\" \/>\n        <path d=\"M 900 100 L 1050 100\" class=\"connection-line\" \/>\n    <\/svg>\n\n    <div class=\"node\" style=\"border-bottom: 3px solid var(--mars);\">\n        <div class=\"node-header\">\n            <div class=\"icon-box mars-color\">\n                <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"10\" width=\"18\" height=\"7\" rx=\"1.5\"\/><circle cx=\"7\" cy=\"19\" r=\"1.8\"\/><circle cx=\"17\" cy=\"19\" r=\"1.8\"\/><path d=\"M12 10V5\"\/><circle cx=\"12\" cy=\"3.5\" r=\"1.4\"\/><\/svg>\n            <\/div>\n            <div class=\"node-title\">Capteurs Rover<\/div>\n        <\/div>\n        <ul class=\"data-list\">\n            <li data-mcz><span>IMAGES BRUTES<\/span><span class=\"data-tag\">CCD 1648\u00d71214<\/span><\/li>\n            <li data-mcz><span>FILTRES<\/span><span class=\"data-tag\">16 (8G+8D)<\/span><\/li>\n            <li data-meda hidden><span>TEMP\u00c9RATURE<\/span><span class=\"data-tag\">ATS<\/span><\/li>\n            <li data-meda hidden><span>PRESSION \/ VENT<\/span><span class=\"data-tag\">PS \/ WS<\/span><\/li>\n            <li><span>CADENCE<\/span><span class=\"data-tag\">900 s<\/span><\/li>\n        <\/ul>\n        <div class=\"tech-stack\">\n            Stack: <span>Python 3.11 + NumPy\/Pillow<\/span><br>\n            rover_simulator (continu)\n            <div class=\"status-row\"><span class=\"status-dot\"><\/span>SYST\u00c8ME ACTIF<\/div>\n        <\/div>\n    <\/div>\n\n    <div class=\"node\" style=\"border-bottom: 3px solid var(--mars);\">\n        <div class=\"node-header\">\n            <div class=\"icon-box mars-color\">\n                <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" rx=\"1.5\"\/><rect x=\"9.5\" y=\"9.5\" width=\"5\" height=\"5\"\/><path d=\"M9 2v3M15 2v3M9 19v3M15 19v3M2 9h3M2 15h3M19 9h3M19 15h3\"\/><\/svg>\n            <\/div>\n            <div class=\"node-title\">Encodage CCSDS<\/div>\n        <\/div>\n        <ul class=\"data-list\">\n            <li><span>PROTOCOLE<\/span><span class=\"data-tag\">CCSDS 133.0-B-2<\/span><\/li>\n            <li data-mcz><span>APID<\/span><span class=\"data-tag\">0x01A5 \/ 0x01A6<\/span><\/li>\n            <li data-meda hidden><span>APID<\/span><span class=\"data-tag\">0x0C0\u20130x0C4<\/span><\/li>\n            <li><span>CHECKSUM<\/span><span class=\"data-tag\">CRC-16\/CCITT<\/span><\/li>\n        <\/ul>\n        <div class=\"tech-stack\">\n            Stack: <span>ccsds_encoder.py<\/span><br>\n            Segmentation + r\u00e9assemblage\n            <div class=\"status-row\"><span class=\"status-dot\"><\/span>SYST\u00c8ME ACTIF<\/div>\n        <\/div>\n    <\/div>\n\n    <div class=\"node\" style=\"border-bottom: 3px solid var(--space);\">\n        <div class=\"node-header\">\n            <div class=\"icon-box space-color\">\n                <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"2\" y=\"10\" width=\"5\" height=\"4\"\/><rect x=\"17\" y=\"10\" width=\"5\" height=\"4\"\/><rect x=\"9.5\" y=\"9\" width=\"5\" height=\"6\" rx=\"1\"\/><path d=\"M7 12h2.5M14.5 12H17\"\/><path d=\"M12 9V6M10 6h4\"\/><\/svg>\n            <\/div>\n            <div class=\"node-title\">Relais MRO<\/div>\n        <\/div>\n        <ul class=\"data-list\">\n            <li><span>LIAISON MONTANTE<\/span><span class=\"data-tag\">UHF<\/span><\/li>\n            <li><span>STOCKAGE &amp; TRANSFERT<\/span><span class=\"data-tag\">M\u00c9M<\/span><\/li>\n            <li><span>LIAISON DESCENDANTE<\/span><span class=\"data-tag\">X-BAND<\/span><\/li>\n        <\/ul>\n        <div class=\"tech-stack\">\n            R\u00e9f: <span>Mars Reconnaissance Orbiter<\/span><br>\n            Relais simul\u00e9\n            <div class=\"status-row\"><span class=\"status-dot\"><\/span>SYST\u00c8ME ACTIF<\/div>\n        <\/div>\n    <\/div>\n\n    <div class=\"node\" style=\"border-bottom: 3px solid var(--neon-cyan);\">\n        <div class=\"node-header\">\n            <div class=\"icon-box earth-color\">\n                <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 15a9 9 0 0115-6\"\/><path d=\"M19 9l-7 6\"\/><circle cx=\"19\" cy=\"9\" r=\"1.4\" fill=\"currentColor\" stroke=\"none\"\/><path d=\"M12 15v4M9 21h6\"\/><\/svg>\n            <\/div>\n            <div class=\"node-title\">DSN Terre<\/div>\n        <\/div>\n        <ul class=\"data-list\">\n            <li><span>STATIONS<\/span><span class=\"data-tag\">Goldstone\/Madrid\/Canberra<\/span><\/li>\n            <li><span>D\u00c9LAI SIMUL\u00c9<\/span><span class=\"data-tag\">3\u201322 MIN<\/span><\/li>\n            <li><span>BRUIT CANAL<\/span><span class=\"data-tag\">BER X-band + perte 2%<\/span><\/li>\n        <\/ul>\n        <div class=\"tech-stack\">\n            Stack: <span>DSNSimulator (Python)<\/span><br>\n            BER + perte de paquets\n            <div class=\"status-row\"><span class=\"status-dot\"><\/span>SYST\u00c8ME ACTIF<\/div>\n        <\/div>\n    <\/div>\n\n    <div class=\"node\" style=\"border-bottom: 3px solid var(--neon-pink);\">\n        <div class=\"node-header\">\n            <div class=\"icon-box process-color\">\n                <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"4\" y=\"3.5\" width=\"16\" height=\"5.5\" rx=\"1\"\/><rect x=\"4\" y=\"15\" width=\"16\" height=\"5.5\" rx=\"1\"\/><circle cx=\"7.5\" cy=\"6.2\" r=\"0.8\" fill=\"currentColor\" stroke=\"none\"\/><circle cx=\"7.5\" cy=\"17.7\" r=\"0.8\" fill=\"currentColor\" stroke=\"none\"\/><path d=\"M4 11.5h16\"\/><\/svg>\n            <\/div>\n            <div class=\"node-title\">Pipeline Airflow<\/div>\n        <\/div>\n        <ul class=\"data-list\">\n            <li><span>ARCHITECTURE<\/span><span class=\"data-tag\">Raw\u2192Bronze\u2192Silver\u2192Gold<\/span><\/li>\n            <li data-mcz><span>DAG<\/span><span class=\"data-tag\">mastcamz_full_pipeline<\/span><\/li>\n            <li data-meda hidden><span>DAG<\/span><span class=\"data-tag\">meda_full_pipeline (11 t\u00e2ches)<\/span><\/li>\n            <li><span>ANOMALIES<\/span><span class=\"data-tag\">d\u00e9tection auto<\/span><\/li>\n        <\/ul>\n        <div class=\"tech-stack\">\n            Stack: <span>Airflow 2.9 \/ dbt-core 1.8<\/span><br>\n            Kafka \u2192 MinIO\/S3 \u2192 PostGIS\n            <div class=\"status-row\"><span class=\"status-dot\"><\/span>SYST\u00c8ME ACTIF<\/div>\n        <\/div>\n    <\/div>\n\n    <div class=\"node\" style=\"border-bottom: 3px solid var(--neon-green);\">\n        <div class=\"node-header\">\n            <div class=\"icon-box viz-color\">\n                <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"4\" width=\"18\" height=\"12\" rx=\"1.5\"\/><path d=\"M8 20h8M12 16v4\"\/><path d=\"M7 9l3 3-3 3M13 14h4\"\/><\/svg>\n            <\/div>\n            <div class=\"node-title\">Ops Mission<\/div>\n        <\/div>\n        <ul class=\"data-list\">\n            <li data-mcz><span>PANNEAU<\/span><span class=\"data-tag\">Couverture images\/sol<\/span><\/li>\n            <li data-meda hidden><span>PANNEAUX<\/span><span class=\"data-tag\">Temp\/Press\/Vent\/Anomalies<\/span><\/li>\n            <li><span>NOTEBOOKS<\/span><span class=\"data-tag\">JupyterHub<\/span><\/li>\n            <li><span>PROVISIONING<\/span><span class=\"data-tag\">versionn\u00e9 (IaC)<\/span><\/li>\n        <\/ul>\n        <div class=\"tech-stack\">\n            Stack: <span>Grafana 10 \/ JupyterHub<\/span><br>\n            Dashboard en temps r\u00e9el\n            <div class=\"status-row\"><span class=\"status-dot\"><\/span>SYST\u00c8ME ACTIF<\/div>\n        <\/div>\n    <\/div>\n\n<\/div>\n\n<div class=\"footer-note\">\n    \/\/ FIN DE LA CARTE DE FLUX \/\/ PIPELINE D\u00c9PLOY\u00c9 \u2014 DOCKER COMPOSE (LOCAL) + TERRAFORM (AWS, HYBRIDE)\n<\/div>\n\n<script>\n(function () {\n    var root = document.getElementById('rover-flux');\n    var buttons = root.querySelectorAll('.toggle-btn');\n    function setPipeline(p) {\n        root.querySelectorAll('[data-mcz]').forEach(function (el) { el.hidden = p !== 'mcz'; });\n        root.querySelectorAll('[data-meda]').forEach(function (el) { el.hidden = p !== 'meda'; });\n        buttons.forEach(function (b) { b.classList.toggle('active', b.dataset.pipeline === p); });\n    }\n    buttons.forEach(function (b) {\n        b.addEventListener('click', function () { setPipeline(b.dataset.pipeline); });\n    });\n})();\n<\/script>\n<\/div>\n\n\n\n<h3 class=\"wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-1\">El problema<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Cada vez que el rover Perseverance toma una foto o mide la temperatura en Marte, esos datos viajan empaquetados en un protocolo espacial real (CCSDS), cruzan entre 3 y 22 minutos de espacio hasta la Tierra, y reci\u00e9n ah\u00ed un equipo de ingenier\u00eda los recibe, valida, calibra y convierte en algo que un cient\u00edfico puede usar. Quise construir ese sistema completo \u2014 no una simulaci\u00f3n de juguete, sino la misma arquitectura que usar\u00eda un equipo de datos profesional para resolver este problema.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"319\" src=\"https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/architecture_diagram-1024x319.png\" alt=\"\" class=\"wp-image-1459\" srcset=\"https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/architecture_diagram-1024x319.png 1024w, https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/architecture_diagram-765x238.png 765w, https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/architecture_diagram-300x93.png 300w, https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/architecture_diagram-1536x478.png 1536w, https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/architecture_diagram-193x60.png 193w, https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/architecture_diagram-2048x637.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"has-small-font-size wp-block-paragraph\">Sistema de informaci\u00f3n &#8211; Arquitectura<\/p>\n\n\n\n<h3 class=\"wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-2\"><br>Qu\u00e9 constru\u00ed<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>El protocolo real, no uno inventado<\/strong>&nbsp;\u2014 un simulador que empaqueta im\u00e1genes y telemetr\u00eda exactamente como especifica el est\u00e1ndar CCSDS 133.0-B-2, con checksum CRC-16 incluido.<\/li>\n\n\n\n<li><strong>Arquitectura Medallion completa<\/strong>&nbsp;\u2014 los datos pasan por capas Raw \u2192 Bronze \u2192 Silver \u2192 Gold, cada una con su responsabilidad: nada se valida dos veces, nada se salta un paso.<\/li>\n\n\n\n<li><strong>Detecci\u00f3n de anomal\u00edas autom\u00e1tica<\/strong>&nbsp;\u2014 el sistema revisa solo cada lectura de temperatura, presi\u00f3n y viento, y marca lo que se sale de rango, sin que nadie tenga que mirar una tabla a mano.<\/li>\n\n\n\n<li><strong>Todo versionado como c\u00f3digo<\/strong>&nbsp;\u2014 no solo el pipeline: el dashboard de monitoreo y la infraestructura en la nube tambi\u00e9n viven en el repositorio, no se armaron a mano en una interfaz y listo.<\/li>\n\n\n\n<li><strong>Infraestructura h\u00edbrida<\/strong>\u00a0\u2014 corre local con Docker, y tiene la variante en AWS (Terraform) lista para desplegar seg\u00fan el costo real de cada servicio.<br><br><\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"409\" src=\"https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/aws_architecture_diagram-1024x409.png\" alt=\"AWS - architecture diagram\" class=\"wp-image-1460\" srcset=\"https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/aws_architecture_diagram-1024x409.png 1024w, https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/aws_architecture_diagram-767x306.png 767w, https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/aws_architecture_diagram-300x120.png 300w, https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/aws_architecture_diagram-2048x817.png 2048w, https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/aws_architecture_diagram-1536x613.png 1536w, https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/aws_architecture_diagram-150x60.png 150w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"has-small-font-size wp-block-paragraph\">AWS &#8211; architecture diagram<\/p>\n\n\n\n<h3 class=\"wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-3\"><br>La prueba<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"549\" src=\"https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/grafana_dashboard-1024x549.png\" alt=\"\" class=\"wp-image-1457\" srcset=\"https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/grafana_dashboard-1024x549.png 1024w, https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/grafana_dashboard-768x412.png 768w, https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/grafana_dashboard-300x161.png 300w, https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/grafana_dashboard-1536x824.png 1536w, https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/grafana_dashboard-112x60.png 112w, https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/grafana_dashboard.png 1737w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><br>El dashboard no muestra datos de ejemplo: la curva de temperatura que ves ah\u00ed reproduce la forma real del modelo f\u00edsico de Marte (m\u00ednimo antes del amanecer, m\u00e1ximo despu\u00e9s del mediod\u00eda marciano), generada por el propio simulador y procesada por el pipeline completo hasta llegar a pantalla.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"303\" src=\"https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/airflow_dag_graph-1024x303.png\" alt=\"\" class=\"wp-image-1458\" srcset=\"https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/airflow_dag_graph-1024x303.png 1024w, https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/airflow_dag_graph-300x89.png 300w, https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/airflow_dag_graph-767x227.png 767w, https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/airflow_dag_graph-203x60.png 203w, https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/airflow_dag_graph-1536x455.png 1536w, https:\/\/javierladino.com\/es\/wp-content\/uploads\/2026\/10\/airflow_dag_graph.png 1716w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"has-small-font-size wp-block-paragraph\">Orquestaci\u00f3n con Airflow<\/p>\n\n\n\n<h3 class=\"wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-4\"><br>Una historia real: c\u00f3mo casi pierdo el dashboard (y por qu\u00e9 no lo vuelvo a perder)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Arm\u00e9 el dashboard de monitoreo directo en la interfaz de Grafana \u2014 parec\u00eda terminado. Al d\u00eda siguiente, los paneles hab\u00edan desaparecido: solo quedaba el t\u00edtulo. La causa real: nada de la configuraci\u00f3n de Grafana estaba guardada como c\u00f3digo, as\u00ed que todo viv\u00eda \u00fanicamente en un volumen interno que cualquier reinicio pod\u00eda borrar sin aviso. La soluci\u00f3n no fue rehacer los paneles a mano otra vez \u2014 fue tratar el dashboard como se trata cualquier otra parte del sistema: escribirlo como archivos versionados (<code>datasource.yml<\/code>, el JSON del dashboard) que se reconstruyen solos cada vez que el servicio arranca. Ahora un reinicio, una migraci\u00f3n de m\u00e1quina, o perder el disco no vuelve a borrar meses de trabajo de configuraci\u00f3n.<\/p>\n\n\n\n<h3 class=\"wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-5\">Metodolog\u00eda: Spec-Driven Development<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Todo el proyecto se construy\u00f3 bajo una metodolog\u00eda de desarrollo dirigido por especificaciones: cada funcionalidad se especifica, se planea y se prueba antes de implementarse, bajo una constituci\u00f3n de 8 principios no negociables (fidelidad al dominio real, arquitectura Medallion sin atajos, verificaci\u00f3n real con tests autom\u00e1ticos, cero secretos escritos a mano, entre otros). Es la forma en la que estoy aprendiendo a trabajar con agentes de IA en proyectos reales: el agente acelera la escritura del c\u00f3digo, pero el entendimiento, las decisiones de arquitectura y la validaci\u00f3n siguen siendo m\u00edos.<\/p>\n\n\n\n<h3 class=\"wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-6\">Stack t\u00e9cnico<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Python \u00b7 Apache Kafka \u00b7 Apache Airflow \u00b7 dbt \u00b7 PostgreSQL\/PostGIS \u00b7 Docker \u00b7 Terraform \u00b7 AWS \u00b7 Grafana \u00b7 GitHub Actions<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/javiladino\/rover_mars\" target=\"_blank\" rel=\"noopener\">Ver el repositorio completo en GitHub<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Repliqu\u00e9, de punta a punta, el sistema que la NASA usa para recibir y procesar las fotos y los datos ambientales del rover Perseverance en Marte. No es un notebook de ejemplo: es un pipeline de datos real, con el protocolo espacial aut\u00e9ntico, corriendo con datos generados en vivo. Cartographie du Flux de Donn\u00e9es ROVER &hellip; <\/p>\n","protected":false},"author":1,"featured_media":1462,"comment_status":"open","ping_status":"closed","template":"","meta":{"ngg_post_thumbnail":0},"rara_portfolio_categories":[35],"class_list":["post-1455","rara-portfolio","type-rara-portfolio","status-publish","has-post-thumbnail","hentry","rara_portfolio_categories-data-engineer"],"_links":{"self":[{"href":"https:\/\/javierladino.com\/es\/wp-json\/wp\/v2\/rara-portfolio\/1455","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/javierladino.com\/es\/wp-json\/wp\/v2\/rara-portfolio"}],"about":[{"href":"https:\/\/javierladino.com\/es\/wp-json\/wp\/v2\/types\/rara-portfolio"}],"author":[{"embeddable":true,"href":"https:\/\/javierladino.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/javierladino.com\/es\/wp-json\/wp\/v2\/comments?post=1455"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/javierladino.com\/es\/wp-json\/wp\/v2\/media\/1462"}],"wp:attachment":[{"href":"https:\/\/javierladino.com\/es\/wp-json\/wp\/v2\/media?parent=1455"}],"wp:term":[{"taxonomy":"rara_portfolio_categories","embeddable":true,"href":"https:\/\/javierladino.com\/es\/wp-json\/wp\/v2\/rara_portfolio_categories?post=1455"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}