- Menos de 20 min
- 2 comunas
- 20 a 35 min
- 1 comuna
- Más de 35 min
- 1 comuna
Tabla alternativa
| Comuna | Tiempo medio |
|---|---|
| La Florida | 12 min |
| Puente Alto | 16 min |
| Pirque | 31 min |
| San José de Maipo | 58 min |
Patrón canónico de mapa incorporado para cochid-graphs, mapas, periodismo2 y firmas y documentos.
Primero el componente, después la variación de marca y finalmente su uso dentro de una página.
<figure class="ui-sample ui-embed ui-map-demo" data-embed="map" data-map-render="inline-demo"><header><strong>Tiempo de acceso al Metro</strong><span>Región Metropolitana, sector suroriente · 2025</span></header><div class="ui-map-demo__frame"><svg viewBox="0 0 560 280" role="img" aria-labelledby="map-title-0 map-desc-0"><title id="map-title-0">Tiempo medio de acceso a una estación de Metro por comuna</title><desc id="map-desc-0">Cuatro comunas coloreadas por tiempo medio de acceso, con la vía principal y dos estaciones marcadas; los valores están en la tabla alternativa.</desc><path class="map-zone map-zone--1" d="M42 46h174l38 65-54 64H58L28 104Z"></path><path class="map-zone map-zone--1" d="M216 46h168l48 74-50 70-128-79Z"></path><path class="map-zone map-zone--3" d="M58 175h142l52-61 130 76-39 57H102Z"></path><path class="map-zone map-zone--4" d="M384 46h110l38 84-62 99-127 18 39-57 50-70Z"></path><path class="map-route" d="M55 212C164 178 225 197 285 135S401 80 510 104"></path><circle class="map-node" cx="285" cy="135" r="7"></circle><circle class="map-node" cx="401" cy="88" r="7"></circle></svg><div class="ui-map-demo__controls"><button class="btn btn--icon" type="button" aria-label="Acercar">+</button><button class="btn btn--icon" type="button" aria-label="Alejar">−</button></div><dl class="ui-map-demo__legend"><div><dt><i class="map-swatch map-swatch--low"></i>Menos de 20 min</dt><dd>2 comunas</dd></div><div><dt><i class="map-swatch map-swatch--mid"></i>20 a 35 min</dt><dd>1 comuna</dd></div><div><dt><i class="map-swatch map-swatch--high"></i>Más de 35 min</dt><dd>1 comuna</dd></div></dl></div><figcaption><span>Fuente: DTPM · 2025 · geometría simplificada</span><a href="#datos-mapa-0">Ver datos del mapa</a></figcaption><details id="datos-mapa-0"><summary>Tabla alternativa</summary><table><thead><tr><th>Comuna</th><th>Tiempo medio</th></tr></thead><tbody><tr><td>La Florida</td><td>12 min</td></tr><tr><td>Puente Alto</td><td>16 min</td></tr><tr><td>Pirque</td><td>31 min</td></tr><tr><td>San José de Maipo</td><td>58 min</td></tr></tbody></table></details></figure>Cada cambio visible declara su autoridad; lo que permanece canónico no se vuelve a inventar en el producto.
Compara la propuesta con cortes reales del repositorio. La ausencia de una muestra se declara y nunca se reconstruye de memoria.
Preparando comparación histórica…
La comparación convierte una regla abstracta en una diferencia visible y verificable.
La misma anatomía debe conservar lectura, foco y acciones sin cortar contenido ni crear scroll horizontal en la página.
| Dimensión | Contrato |
|---|---|
| Propietario canónico | Sistema de diseño CIS y servicio productor · core/core-style |
| Consumidores | cochid-graphs, Mapas, Periodismo2 y Firmas y Documentos |
| Dependencias | CSP, sandbox, fallback enlazable |
| Estado | candidato |
Patrón propuesto. 0 implementaciones observadas en 0 repositorios.
La observación demuestra uso existente; la aceptación canónica sigue dependiendo de esta ficha y de su propietario.
| Dato | Resultado |
|---|---|
| Clasificación | Patrón propuesto |
| Implementaciones | 0 implementaciones observadas |
| Repositorios | Ningún repositorio observado |
| Snapshot | kit/componentes/componentes-observados.json |
El inventario conserva repositorio, archivo fuente y criterio de asociación. Observar una implementación no la convierte automáticamente en canónica.
| Estado requerido |
|---|
| cargando |
| disponible |
| bloqueado |
| error |
| sin JavaScript |
Copia el bloque completo; después cambia contenido y destinos, no su anatomía.
<figure class="ui-sample ui-embed ui-map-demo" data-embed="map" data-map-render="inline-demo"><header><strong>Tiempo de acceso al Metro</strong><span>Región Metropolitana, sector suroriente · 2025</span></header><div class="ui-map-demo__frame"><svg viewBox="0 0 560 280" role="img" aria-labelledby="map-title-0 map-desc-0"><title id="map-title-0">Tiempo medio de acceso a una estación de Metro por comuna</title><desc id="map-desc-0">Cuatro comunas coloreadas por tiempo medio de acceso, con la vía principal y dos estaciones marcadas; los valores están en la tabla alternativa.</desc><path class="map-zone map-zone--1" d="M42 46h174l38 65-54 64H58L28 104Z"></path><path class="map-zone map-zone--1" d="M216 46h168l48 74-50 70-128-79Z"></path><path class="map-zone map-zone--3" d="M58 175h142l52-61 130 76-39 57H102Z"></path><path class="map-zone map-zone--4" d="M384 46h110l38 84-62 99-127 18 39-57 50-70Z"></path><path class="map-route" d="M55 212C164 178 225 197 285 135S401 80 510 104"></path><circle class="map-node" cx="285" cy="135" r="7"></circle><circle class="map-node" cx="401" cy="88" r="7"></circle></svg><div class="ui-map-demo__controls"><button class="btn btn--icon" type="button" aria-label="Acercar">+</button><button class="btn btn--icon" type="button" aria-label="Alejar">−</button></div><dl class="ui-map-demo__legend"><div><dt><i class="map-swatch map-swatch--low"></i>Menos de 20 min</dt><dd>2 comunas</dd></div><div><dt><i class="map-swatch map-swatch--mid"></i>20 a 35 min</dt><dd>1 comuna</dd></div><div><dt><i class="map-swatch map-swatch--high"></i>Más de 35 min</dt><dd>1 comuna</dd></div></dl></div><figcaption><span>Fuente: DTPM · 2025 · geometría simplificada</span><a href="#datos-mapa-0">Ver datos del mapa</a></figcaption><details id="datos-mapa-0"><summary>Tabla alternativa</summary><table><thead><tr><th>Comuna</th><th>Tiempo medio</th></tr></thead><tbody><tr><td>La Florida</td><td>12 min</td></tr><tr><td>Puente Alto</td><td>16 min</td></tr><tr><td>Pirque</td><td>31 min</td></tr><tr><td>San José de Maipo</td><td>58 min</td></tr></tbody></table></details></figure>Copia el bloque completo; después cambia contenido y destinos, no su anatomía.
<figure class="ui-sample ui-embed ui-map-demo border border-slate-300 bg-white p-4 text-slate-950 grid gap-3" data-embed="map" data-map-render="inline-demo"><header class="flex flex-wrap items-center justify-between gap-3"><strong>Tiempo de acceso al Metro</strong><span>Región Metropolitana, sector suroriente · 2025</span></header><div class="ui-map-demo__frame"><svg class="h-auto w-full" viewBox="0 0 560 280" role="img" aria-labelledby="map-title-0 map-desc-0"><title id="map-title-0">Tiempo medio de acceso a una estación de Metro por comuna</title><desc id="map-desc-0">Cuatro comunas coloreadas por tiempo medio de acceso, con la vía principal y dos estaciones marcadas; los valores están en la tabla alternativa.</desc><path class="map-zone map-zone--1" d="M42 46h174l38 65-54 64H58L28 104Z"></path><path class="map-zone map-zone--1" d="M216 46h168l48 74-50 70-128-79Z"></path><path class="map-zone map-zone--3" d="M58 175h142l52-61 130 76-39 57H102Z"></path><path class="map-zone map-zone--4" d="M384 46h110l38 84-62 99-127 18 39-57 50-70Z"></path><path class="map-route" d="M55 212C164 178 225 197 285 135S401 80 510 104"></path><circle class="map-node" cx="285" cy="135" r="7"></circle><circle class="map-node" cx="401" cy="88" r="7"></circle></svg><div class="ui-map-demo__controls"><button class="btn btn--icon border border-slate-950 px-3 py-2 focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed" type="button" aria-label="Acercar">+</button><button class="btn btn--icon border border-slate-950 px-3 py-2 focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed" type="button" aria-label="Alejar">−</button></div><dl class="ui-map-demo__legend grid gap-2"><div><dt><i class="map-swatch map-swatch--low"></i>Menos de 20 min</dt><dd>2 comunas</dd></div><div><dt><i class="map-swatch map-swatch--mid"></i>20 a 35 min</dt><dd>1 comuna</dd></div><div><dt><i class="map-swatch map-swatch--high"></i>Más de 35 min</dt><dd>1 comuna</dd></div></dl></div><figcaption class="text-sm text-slate-600"><span>Fuente: DTPM · 2025 · geometría simplificada</span><a class="underline underline-offset-4" href="#datos-mapa-0">Ver datos del mapa</a></figcaption><details id="datos-mapa-0"><summary>Tabla alternativa</summary><table class="w-full border-collapse text-left"><thead><tr><th class="border-b border-slate-300 p-2 align-top">Comuna</th><th class="border-b border-slate-300 p-2 align-top">Tiempo medio</th></tr></thead><tbody><tr><td class="border-b border-slate-300 p-2 align-top">La Florida</td><td class="border-b border-slate-300 p-2 align-top">12 min</td></tr><tr><td class="border-b border-slate-300 p-2 align-top">Puente Alto</td><td class="border-b border-slate-300 p-2 align-top">16 min</td></tr><tr><td class="border-b border-slate-300 p-2 align-top">Pirque</td><td class="border-b border-slate-300 p-2 align-top">31 min</td></tr><tr><td class="border-b border-slate-300 p-2 align-top">San José de Maipo</td><td class="border-b border-slate-300 p-2 align-top">58 min</td></tr></tbody></table></details></figure>Copia el bloque completo; después cambia contenido y destinos, no su anatomía.
import type { MouseEventHandler, ReactNode } from "react";
export interface MapEmbedLegendItem {
/** Rango o categoría que representa el color. */
label: string;
/** Cuántas zonas caen en el rango, ya formateado («2 comunas»); vacío lo omite. */
value: string;
/** Tono de la muestra de color: bajo (`map-zone--1`), medio (`map-zone--3`) o alto (`map-zone--4`). Sin él, la muestra toma `color`. */
level?: "low" | "mid" | "high";
/** Color de la muestra, para una zona que no calza con `level`, como `map-zone--2`; tiene prioridad sobre el de `level`. Va como `fill` de un SVG dentro de la muestra (no como estilo en línea): acepta un color literal (`#79c8ae`, `rgb(…)`), no `var()`; para un color del kit usa `level`. */
color?: string;
}
export interface MapEmbedRow {
/** Primera celda de la fila (zona, comuna, región). */
label: string;
/** Una celda por cada columna después de la primera, con el valor ya formateado. */
values: string[];
}
export interface MapEmbedProps {
/** Título visible sobre el mapa; si `title` y `detail` están vacíos no se emite el encabezado. */
title?: string;
/** Territorio y período bajo el título. */
detail?: string;
/** Nombre accesible del SVG (`<title>`): qué mide el mapa. */
mapTitle?: string;
/** Descripción accesible del SVG (`<desc>`); vacía la omite. */
mapDescription?: string;
/** Geometría del mapa dentro del SVG (zonas, rutas, nodos); sin ella se dibuja la de la muestra. */
children?: ReactNode;
/** Sistema de coordenadas del SVG (`viewBox`); debe coincidir con la geometría de `children`. */
viewBox?: string;
/** Muestra los botones de acercar y alejar. */
controls?: boolean;
zoomInLabel?: string;
zoomOutLabel?: string;
onZoomIn?: MouseEventHandler<HTMLButtonElement>;
onZoomOut?: MouseEventHandler<HTMLButtonElement>;
/** Leyenda de colores junto al mapa; sin elementos no se muestra. */
legend?: MapEmbedLegendItem[];
/** Línea de fuente del pie, sin el separador final. */
source?: string;
/** Texto del enlace del pie; por defecto lleva a la tabla alternativa. */
linkLabel?: string;
/** Destino del enlace del pie; por defecto `#${dataId}`. Sin `href` ni filas no hay enlace. */
href?: string;
/** Texto del control que despliega la tabla alternativa; vacío omite el `<summary>`. */
dataLabel?: string;
/** Muestra la tabla alternativa desplegada (atributo nativo `open` de `<details>`). */
dataOpen?: boolean;
/** Encabezados de la tabla alternativa; el primero corresponde a la primera celda de cada fila. */
columns?: string[];
/** Filas de la tabla alternativa, con los mismos datos que dibuja el mapa; sin filas no hay tabla. */
rows?: MapEmbedRow[];
/** La primera celda de cada fila va como encabezado de fila (`<th>`); por defecto no. */
rowHeaders?: boolean;
/** Id de la figura; los ids internos pasan a ser `${id}-titulo`, `${id}-descripcion` y `${id}-datos`. */
id?: string;
titleId?: string;
descriptionId?: string;
/** Id de la tabla alternativa, destino del enlace del pie. */
dataId?: string;
/** Modo de render del mapa (`data-map-render`); vacío omite el atributo. */
renderMode?: string;
className?: string;
}
// Cuatro comunas: dos en map-zone--1 (menos de 20 min), una en --3 y una en --4,
// las mismas que cuentan la leyenda y la tabla alternativa.
const GEOMETRIA = (
<>
<path className="map-zone map-zone--1" d="M42 46h174l38 65-54 64H58L28 104Z" />
<path className="map-zone map-zone--1" d="M216 46h168l48 74-50 70-128-79Z" />
<path className="map-zone map-zone--3" d="M58 175h142l52-61 130 76-39 57H102Z" />
<path className="map-zone map-zone--4" d="M384 46h110l38 84-62 99-127 18 39-57 50-70Z" />
<path className="map-route" d="M55 212C164 178 225 197 285 135S401 80 510 104" />
<circle className="map-node" cx="285" cy="135" r="7" />
<circle className="map-node" cx="401" cy="88" r="7" />
</>
);
const LEYENDA: MapEmbedLegendItem[] = [
{ label: "Menos de 20 min", value: "2 comunas", level: "low" },
{ label: "20 a 35 min", value: "1 comuna", level: "mid" },
{ label: "Más de 35 min", value: "1 comuna", level: "high" },
];
const COLUMNAS = ["Comuna", "Tiempo medio"];
const FILAS: MapEmbedRow[] = [
{ label: "La Florida", values: ["12 min"] },
{ label: "Puente Alto", values: ["16 min"] },
{ label: "Pirque", values: ["31 min"] },
{ label: "San José de Maipo", values: ["58 min"] },
];
/**
* Mapa incorporado: mapa temático en SVG para mostrar un indicador por zona dentro de una página, con título, controles de zoom, leyenda de colores, fuente y tabla alternativa con los mismos datos.
*
* El SVG tiene `role="img"` y se nombra con su `<title>` y `<desc>`; la tabla alternativa bajo el mapa es la lectura accesible de los valores. Usa un `id` distinto por mapa en la página.
*
* @example <MapEmbed id="acceso-rm" title="Tiempo de acceso al Metro" detail="Región Metropolitana · 2025" rows={[{ label: "La Florida", values: ["12 min"] }, { label: "Pirque", values: ["31 min"] }]}>{zonas}</MapEmbed>
* @example <MapEmbed legend={[{ label: "20 a 25 min", value: "1 comuna", color: "#79c8ae" }]}>{zonas}</MapEmbed>
*/
export function MapEmbed({
title = "Tiempo de acceso al Metro",
detail = "Región Metropolitana, sector suroriente · 2025",
mapTitle = "Tiempo medio de acceso a una estación de Metro por comuna",
mapDescription = "Cuatro comunas coloreadas por tiempo medio de acceso, con la vía principal y dos estaciones marcadas; los valores están en la tabla alternativa.",
children = GEOMETRIA,
viewBox = "0 0 560 280",
controls = true,
zoomInLabel = "Acercar",
zoomOutLabel = "Alejar",
onZoomIn,
onZoomOut,
legend = LEYENDA,
source = "Fuente: DTPM · 2025 · geometría simplificada",
linkLabel = "Ver datos del mapa",
href,
dataLabel = "Tabla alternativa",
dataOpen,
columns = COLUMNAS,
rows = FILAS,
rowHeaders = false,
id,
titleId,
descriptionId,
dataId,
renderMode = "inline-demo",
className,
}: MapEmbedProps) {
const tituloId = titleId ?? (id ? `${id}-titulo` : "map-title-0");
const descripcionId = descriptionId ?? (id ? `${id}-descripcion` : "map-desc-0");
const datosId = dataId ?? (id ? `${id}-datos` : "datos-mapa-0");
const enlace = linkLabel && (href || rows.length) ? <a href={href || `#${datosId}`}>{linkLabel}</a> : null;
return (
<figure
className={["ui-embed ui-map-demo", className].filter(Boolean).join(" ")}
id={id}
data-embed="map"
data-map-render={renderMode || undefined}
>
{title || detail ? (
<header>
{title ? <strong>{title}</strong> : null}
{detail ? <span>{detail}</span> : null}
</header>
) : null}
<div className="ui-map-demo__frame">
<svg viewBox={viewBox} role="img" aria-labelledby={mapDescription ? `${tituloId} ${descripcionId}` : tituloId}>
<title id={tituloId}>{mapTitle}</title>
{mapDescription ? <desc id={descripcionId}>{mapDescription}</desc> : null}
{children}
</svg>
{controls ? (
<div className="ui-map-demo__controls">
<button className="btn btn--icon" type="button" aria-label={zoomInLabel} onClick={onZoomIn}>+</button>
<button className="btn btn--icon" type="button" aria-label={zoomOutLabel} onClick={onZoomOut}>−</button>
</div>
) : null}
{legend.length ? (
<dl className="ui-map-demo__legend">
{legend.map((item, i) => (
<div key={i}>
<dt>
<i className={item.level && !item.color ? `map-swatch map-swatch--${item.level}` : "map-swatch"}>
{item.color ? (
<svg className="map-swatch__color" viewBox="0 0 1 1" preserveAspectRatio="none" aria-hidden="true" focusable="false">
<rect width="1" height="1" fill={item.color} />
</svg>
) : null}
</i>
{item.label}
</dt>
{item.value ? <dd>{item.value}</dd> : null}
</div>
))}
</dl>
) : null}
</div>
{source || enlace ? (
<figcaption>
{source ? <span>{source}</span> : null}
{enlace}
</figcaption>
) : null}
{rows.length ? (
<details id={datosId} open={dataOpen}>
{dataLabel ? <summary>{dataLabel}</summary> : null}
<table>
{columns.length ? (
<thead>
<tr>{columns.map((columna, i) => <th key={i}>{columna}</th>)}</tr>
</thead>
) : null}
<tbody>
{rows.map((fila, i) => (
<tr key={i}>
{rowHeaders ? <th>{fila.label}</th> : <td>{fila.label}</td>}
{fila.values.map((valor, j) => <td key={j}>{valor}</td>)}
</tr>
))}
</tbody>
</table>
</details>
) : null}
</figure>
);
}