Fix etapa 3
This commit is contained in:
@@ -20,6 +20,9 @@ Al cerrar un sprint, los ítems `[SPRINT-N]` se promocionan a `[SPRINT-(N+1)]` o
|
||||
|
||||
## Features de alta prioridad
|
||||
|
||||
### [BACKLOG] Indicador de simulación desactualizada
|
||||
Cuando el usuario guarda cambios en una actividad (costos, recursos, automatización) pero no vuelve a simular, el reporte muestra resultados del estado anterior sin ningún aviso. Agregar un indicador sutil ("Hay cambios sin simular" o badge en el botón Simular) que desaparezca al ejecutar la simulación. Detectado en uso real durante Sprint 2 — genera confusión al no ver los nuevos costos reflejados en el reporte.
|
||||
|
||||
### [BACKLOG] Múltiples simulaciones por proceso
|
||||
Historial de simulaciones con comparación de escenarios.
|
||||
|
||||
@@ -54,6 +57,9 @@ Manifest + service worker para uso offline.
|
||||
### [BACKLOG] Zoom/pan del heatmap desde el reporte
|
||||
Hoy es visualización estática. Permitir interacción.
|
||||
|
||||
### [BACKLOG] Controles de zoom en canvas BPMN (fit-to-screen / reset zoom)
|
||||
El canvas puede quedar muy zoomed out sin forma de volver al estado inicial. bpmn-js expone `canvas.zoom('fit-viewport')` — agregar botón "Encuadrar diagrama" o equivalente. Detectado en validación visual Sprint 2 Etapa 3.
|
||||
|
||||
### [BACKLOG] Detalle de recursos por actividad en el PDF
|
||||
Tabla de costos por recurso asignado dentro de cada actividad.
|
||||
|
||||
|
||||
237
sprints/sprint-2/ETAPA_3_PROMPT.md
Normal file
237
sprints/sprint-2/ETAPA_3_PROMPT.md
Normal file
@@ -0,0 +1,237 @@
|
||||
# Sprint 2 — Etapa 3: Reporte web + cierre de deuda técnica
|
||||
|
||||
> **Tipo:** UI de reporte + deuda técnica. Sin cambios en dominio, persistencia ni workspace.
|
||||
> **Objetivo:** agregar desglose de recursos por actividad en la tabla del reporte, y cerrar los tres ítems de TECH_DEBT pendientes (CumulativeSavingsChart, payback label, badge ⚡).
|
||||
> **Resultado esperado:** tabla con filas expandibles mostrando el desglose de costos, gráfico financieramente correcto, badge evaluado. Validación visual del director requerida antes del OK formal.
|
||||
|
||||
---
|
||||
|
||||
## Archivos a modificar
|
||||
|
||||
```
|
||||
src/features/report/ActivitiesTable.tsx ← principal (expandable rows)
|
||||
src/features/report/CumulativeSavingsChart.tsx ← verificar -inversión + payback label
|
||||
src/features/workspace/BpmnCanvas.tsx ← badge ⚡ tamaño (si la decisión es cambiar)
|
||||
```
|
||||
|
||||
Si durante la implementación identificás que otro archivo necesita cambio, **detenete y reportá el conflicto antes de tocarlo**.
|
||||
|
||||
---
|
||||
|
||||
## Feature 1 — ActivitiesTable: filas expandibles con desglose de recursos
|
||||
|
||||
### Contexto
|
||||
|
||||
`ActivitySimResult` ya incluye `resourceCostBreakdown: Array<{ resourceId, resourceName, cost }>`. Los datos están disponibles — solo falta mostrarlos.
|
||||
|
||||
El `expectedDirectCost` actual ya incluye el costo de recursos (es `fixedCost + resourceCost`). Lo que la tabla no muestra es el desglose interno de ese costo directo.
|
||||
|
||||
### Comportamiento esperado
|
||||
|
||||
- Cada fila de la tabla tiene un chevron expandible a la izquierda del indicador de color
|
||||
- Al hacer click en la fila (o en el chevron), se expande una fila secundaria inline
|
||||
- La fila expandida muestra el desglose del costo directo:
|
||||
- Fijo (insumos, licencias): `directCostFixed` — **nota:** este valor no está en `ActivitySimResult`, ver abajo
|
||||
- Cada recurso: nombre + costo esperado (ya en `resourceCostBreakdown[].cost`)
|
||||
- Si `resourceCostBreakdown` está vacío, la fila sigue siendo expandible pero muestra "Sin recursos asignados — costo es 100% fijo"
|
||||
- Si `resourceCostBreakdown` está vacío Y `expectedDirectCost === 0`, no mostrar chevron (no hay desglose útil)
|
||||
|
||||
### Sobre el costo fijo vs. costo de recursos
|
||||
|
||||
`ActivitySimResult` tiene `expectedDirectCost` (total) y `resourceCostBreakdown` (recursos). El costo fijo esperado = `expectedDirectCost - sum(resourceCostBreakdown[].cost)`.
|
||||
|
||||
**No tocar el motor de simulación ni los tipos.** Calcular el costo fijo esperado en el componente con esa resta.
|
||||
|
||||
### Cambios en ActivitiesTable.tsx
|
||||
|
||||
**Agregar al estado local:**
|
||||
```tsx
|
||||
const [expandedRows, setExpandedRows] = useState<Set<string>>(new Set())
|
||||
|
||||
function toggleRow(activityId: string) {
|
||||
setExpandedRows(prev => {
|
||||
const next = new Set(prev)
|
||||
if (next.has(activityId)) next.delete(activityId)
|
||||
else next.add(activityId)
|
||||
return next
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
**Cambio en el header:** reemplazar la columna de color `<TableHead className="w-8" />` por una columna ligeramente más ancha que incluye tanto el chevron como el indicador de color. O bien agregar una columna de chevron antes del indicador.
|
||||
|
||||
**En cada fila:** reemplazar el `onClick={() => onRowClick(act.bpmnElementId)}` para manejar expansión Y selección en el heatmap. La expansión se dispara desde el chevron, el click en la fila sigue navegando al heatmap.
|
||||
|
||||
**Fila expandida** (insertar como `<TableRow>` adicional inmediatamente después de la fila principal cuando está expandida):
|
||||
```tsx
|
||||
{isExpanded && (
|
||||
<TableRow className="bg-slate-50/70 hover:bg-slate-50/70">
|
||||
<TableCell /> {/* chevron vacío */}
|
||||
<TableCell colSpan={7} className="py-2 pl-4">
|
||||
<div className="space-y-1">
|
||||
{/* Costo fijo */}
|
||||
{fixedCostExpected > 0 && (
|
||||
<div className="flex justify-between text-xs text-slate-500">
|
||||
<span>Fijo (insumos, licencias)</span>
|
||||
<span className="font-mono">{formatCurrency(fixedCostExpected, currency)}</span>
|
||||
</div>
|
||||
)}
|
||||
{/* Recursos */}
|
||||
{act.resourceCostBreakdown.map((r) => (
|
||||
<div key={r.resourceId} className="flex justify-between text-xs text-slate-500">
|
||||
<span>{r.resourceName}</span>
|
||||
<span className="font-mono">{formatCurrency(r.cost, currency)}</span>
|
||||
</div>
|
||||
))}
|
||||
{/* Si solo hay costo fijo */}
|
||||
{act.resourceCostBreakdown.length === 0 && fixedCostExpected === 0 && (
|
||||
<p className="text-xs text-slate-400">Sin desglose disponible</p>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
```
|
||||
|
||||
### Versión con wow
|
||||
|
||||
El chevron rota 90° al expandir (transición CSS). El color del desglose de recursos usa un tono ligeramente más cálido para el total (naranja muy sutil, no el naranja InQ completo).
|
||||
|
||||
---
|
||||
|
||||
## Feature 2 — CumulativeSavingsChart: verificar -inversión + fix payback label
|
||||
|
||||
### 2.1 Verificación de -inversión
|
||||
|
||||
Antes de tocar el archivo, verificar la línea:
|
||||
```typescript
|
||||
const values = years.map((y) => roi.annualSavings * y - investment)
|
||||
```
|
||||
|
||||
Cuando `y=0`: `0 - investment = -investment`. **Si esto es lo que hay en el código, el ítem de TECH_DEBT ya está resuelto.** Documentarlo en el reporte y cerrar el ítem. No hacer ningún cambio en ese aspecto.
|
||||
|
||||
Si por alguna razón el código es diferente (ej: `roi.annualSavings * y` sin restar la inversión), corregirlo.
|
||||
|
||||
### 2.2 Fix del payback label superpuesto
|
||||
|
||||
El label actual tiene `position: 'insideEndTop'`. Cuando el payback cae en años tempranos (cerca de x=0), el label se superpone con los labels del eje Y izquierdo.
|
||||
|
||||
Cambiar a `position: 'end'` con un offset explícito, o usar `position: 'insideStartTop'` con padding. La solución más robusta:
|
||||
|
||||
```typescript
|
||||
label: {
|
||||
formatter: `Payback\n${roi.paybackYears.toFixed(1)} años`,
|
||||
position: 'insideEndTop',
|
||||
fontSize: 10,
|
||||
color: '#16A34A',
|
||||
padding: [0, 0, 0, 8], // padding izquierdo para separar del eje Y
|
||||
offset: [4, 0],
|
||||
},
|
||||
```
|
||||
|
||||
Verificar visualmente que el label no colisione con el eje Y en casos de payback temprano (año 1) ni tardío (año 3).
|
||||
|
||||
---
|
||||
|
||||
## Feature 3 — Badge ⚡: evaluar tamaño
|
||||
|
||||
### Contexto
|
||||
|
||||
El badge actual es 14×14px (`width:14px; height:14px; line-height:14px` en `makeBadgeHtml()`). El director observó "podría ser un poquito más grande".
|
||||
|
||||
### Decisión a tomar en la implementación
|
||||
|
||||
1. Cambiar a 16×16px
|
||||
2. Verificar visualmente que no colisione con bordes de nodos chicos del BPMN (los nodos de evento son típicamente 36px de diámetro)
|
||||
3. Si no hay colisión: mantener 16×16px y reportar
|
||||
4. Si hay colisión visible: revertir a 14×14px y documentar por qué
|
||||
|
||||
El badge se posiciona 8px fuera del borde derecho del nodo. Con 16×16px, verificar también que ese offset siga siendo visualmente correcto.
|
||||
|
||||
---
|
||||
|
||||
## Verificaciones obligatorias antes de reportar
|
||||
|
||||
### 1. Verificación de scope con git diff — OBLIGATORIO
|
||||
```bash
|
||||
git diff --name-only HEAD
|
||||
```
|
||||
|
||||
El resultado debe contener únicamente:
|
||||
```
|
||||
src/features/report/ActivitiesTable.tsx
|
||||
src/features/report/CumulativeSavingsChart.tsx
|
||||
src/features/workspace/BpmnCanvas.tsx ← solo si se cambió el badge
|
||||
```
|
||||
|
||||
Si aparece cualquier archivo no listado:
|
||||
1. Revertilo: `git checkout HEAD -- <archivo>`
|
||||
2. Listarlo bajo "Conflictos de scope" en el reporte
|
||||
3. No incluirlo en el entregable
|
||||
|
||||
### 2. Build limpio
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
Sin errores TypeScript.
|
||||
|
||||
### 3. Tests
|
||||
```bash
|
||||
npm run test
|
||||
```
|
||||
Todos los tests existentes deben seguir verdes. Esta etapa no agrega tests nuevos (cambios de UI pura y fix trivial de layout).
|
||||
|
||||
### 4. Smoke test visual
|
||||
- [ ] Tabla del reporte: chevron visible en actividades con datos
|
||||
- [ ] Expandir una actividad con recursos: muestra desglose fijo + recursos
|
||||
- [ ] Expandir una actividad sin recursos: muestra "Sin recursos asignados"
|
||||
- [ ] CumulativeSavingsChart: el gráfico arranca debajo de 0 cuando hay inversión
|
||||
- [ ] Payback label: no colisiona con eje Y
|
||||
- [ ] Badge ⚡: tamaño correcto, sin colisión con nodos chicos
|
||||
|
||||
---
|
||||
|
||||
## Alcance estricto — lo que NO entra en esta etapa
|
||||
|
||||
- ❌ `ActivityPanel.tsx` ni `ResourcesPanel.tsx` — Etapa 2 ya los cerró
|
||||
- ❌ Edición inline de proceso/cliente — Etapa 4
|
||||
- ❌ PDFs — Etapa 5
|
||||
- ❌ Indicador "datos pendientes de simular" — BACKLOG (requiere decisión de UX)
|
||||
- ❌ `ComparisonTable.tsx` — no está en scope de esta etapa
|
||||
- ❌ Cualquier cambio en dominio (`src/domain/`) o persistencia (`src/persistence/`)
|
||||
|
||||
---
|
||||
|
||||
## Reporte esperado al terminar
|
||||
|
||||
```
|
||||
## Etapa 3 completada
|
||||
|
||||
### Archivos modificados
|
||||
- src/features/report/ActivitiesTable.tsx — filas expandibles con desglose
|
||||
- src/features/report/CumulativeSavingsChart.tsx — [qué se cambió]
|
||||
- src/features/workspace/BpmnCanvas.tsx — [badge 14→16px / mantenido en 14px, razón]
|
||||
|
||||
### Cambios realizados
|
||||
- ActivitiesTable: expandable rows con chevron, desglose fijo + recursos
|
||||
- CumulativeSavingsChart -inversión: [ya estaba correcto / corregido]
|
||||
- Payback label: offset ajustado, sin colisión
|
||||
- Badge: [16px sin colisión / mantenido 14px por colisión en nodos chicos]
|
||||
|
||||
### TECH_DEBT cerrados
|
||||
- CumulativeSavingsChart arranca en -inversión: ✅ [ya estaba / corregido]
|
||||
- Payback label superpuesto: ✅ corregido
|
||||
- Badge ⚡ tamaño: ✅ [16px / 14px mantenido con razón]
|
||||
|
||||
### Verificaciones
|
||||
- git diff --name-only HEAD: [lista de archivos] ✅
|
||||
- npm run build: ✅
|
||||
- npm run test: X/X tests verdes ✅
|
||||
- Smoke test: [descripción breve]
|
||||
|
||||
### Conflictos de scope
|
||||
[ninguno / lista de archivos revertidos y razón]
|
||||
|
||||
### Notas para Etapas siguientes
|
||||
[hallazgos para Etapas 4+]
|
||||
```
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react'
|
||||
import { ArrowUpDown, ArrowUp, ArrowDown, ChevronRight } from 'lucide-react'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||
import { activityColor } from '@/lib/colors'
|
||||
import { formatCurrency, formatPercent, formatMinutes } from '@/lib/format'
|
||||
@@ -26,6 +26,7 @@ function SortIcon({ column, sortKey, dir }: { column: SortKey; sortKey: SortKey;
|
||||
export function ActivitiesTable({ perActivity, mode, currency, highlightedId, onRowClick }: ActivitiesTableProps) {
|
||||
const [sortKey, setSortKey] = useState<SortKey>('expectedTotalCost')
|
||||
const [sortDir, setSortDir] = useState<'asc' | 'desc'>('desc')
|
||||
const [expandedRows, setExpandedRows] = useState<Set<string>>(new Set())
|
||||
const rowRefs = useRef(new Map<string, HTMLTableRowElement>())
|
||||
|
||||
function handleSort(key: SortKey) {
|
||||
@@ -37,6 +38,15 @@ export function ActivitiesTable({ perActivity, mode, currency, highlightedId, on
|
||||
}
|
||||
}
|
||||
|
||||
function toggleRow(activityId: string) {
|
||||
setExpandedRows(prev => {
|
||||
const next = new Set(prev)
|
||||
if (next.has(activityId)) next.delete(activityId)
|
||||
else next.add(activityId)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
const sorted = useMemo(() => {
|
||||
return [...perActivity].sort((a, b) => {
|
||||
const av = a[sortKey]
|
||||
@@ -73,7 +83,7 @@ export function ActivitiesTable({ perActivity, mode, currency, highlightedId, on
|
||||
<Table>
|
||||
<TableHeader className="bg-slate-50">
|
||||
<tr>
|
||||
<TableHead className="w-8 text-xs" />
|
||||
<TableHead className="w-10 text-xs" />
|
||||
<Th label="Actividad" column="activityName" />
|
||||
<Th label="Costo directo" column="expectedDirectCost" />
|
||||
<Th label="Overhead" column="expectedIndirectCost" />
|
||||
@@ -87,43 +97,99 @@ export function ActivitiesTable({ perActivity, mode, currency, highlightedId, on
|
||||
{sorted.map((act) => {
|
||||
const color = activityColor(act, perActivity, mode)
|
||||
const isHighlighted = act.bpmnElementId === highlightedId
|
||||
const isExpanded = expandedRows.has(act.activityId)
|
||||
|
||||
// Costo fijo esperado = total directo − suma de costos de recursos
|
||||
const resourceTotal = act.resourceCostBreakdown.reduce((s, r) => s + r.cost, 0)
|
||||
const fixedCostExpected = act.expectedDirectCost - resourceTotal
|
||||
|
||||
// Mostrar chevron a menos que no haya absolutamente nada que mostrar
|
||||
const hasBreakdown = !(act.resourceCostBreakdown.length === 0 && act.expectedDirectCost === 0)
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
key={act.activityId}
|
||||
ref={(el) => { if (el) rowRefs.current.set(act.bpmnElementId, el) }}
|
||||
onClick={() => onRowClick(act.bpmnElementId)}
|
||||
className={`cursor-pointer ${isHighlighted ? 'bg-primary/5 ring-1 ring-inset ring-primary/20' : ''}`}
|
||||
data-state={isHighlighted ? 'selected' : undefined}
|
||||
>
|
||||
<TableCell className="py-2">
|
||||
<div
|
||||
className="w-3 h-3 rounded-sm flex-shrink-0"
|
||||
style={{ backgroundColor: color }}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-xs font-medium text-slate-800 max-w-48 truncate">
|
||||
{act.activityName}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs font-mono text-slate-600">
|
||||
{formatCurrency(act.expectedDirectCost, currency)}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs font-mono text-slate-500">
|
||||
{formatCurrency(act.expectedIndirectCost, currency)}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs font-mono font-semibold text-slate-800">
|
||||
{formatCurrency(act.expectedTotalCost, currency)}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs font-mono text-slate-600">
|
||||
{formatPercent(act.percentOfTotal)}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs font-mono text-slate-500">
|
||||
{act.executionTimeMinutes > 0 ? formatMinutes(act.executionTimeMinutes) : '—'}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs font-mono text-slate-500">
|
||||
{formatPercent(act.executionProbability * 100)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<>
|
||||
<TableRow
|
||||
key={act.activityId}
|
||||
ref={(el) => { if (el) rowRefs.current.set(act.bpmnElementId, el) }}
|
||||
onClick={() => onRowClick(act.bpmnElementId)}
|
||||
className={`cursor-pointer ${isHighlighted ? 'bg-primary/5 ring-1 ring-inset ring-primary/20' : ''}`}
|
||||
data-state={isHighlighted ? 'selected' : undefined}
|
||||
>
|
||||
{/* Chevron + indicador de color en la misma celda */}
|
||||
<TableCell className="py-2 pr-1">
|
||||
<div className="flex items-center gap-1">
|
||||
{hasBreakdown ? (
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); toggleRow(act.activityId) }}
|
||||
className="text-slate-400 hover:text-slate-600 transition-colors flex-shrink-0"
|
||||
aria-label={isExpanded ? 'Colapsar desglose' : 'Expandir desglose'}
|
||||
>
|
||||
<ChevronRight
|
||||
className="h-3.5 w-3.5 transition-transform duration-150"
|
||||
style={{ transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)' }}
|
||||
/>
|
||||
</button>
|
||||
) : (
|
||||
<span className="w-3.5" />
|
||||
)}
|
||||
<div
|
||||
className="w-3 h-3 rounded-sm flex-shrink-0"
|
||||
style={{ backgroundColor: color }}
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-xs font-medium text-slate-800 max-w-48 truncate">
|
||||
{act.activityName}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs font-mono text-slate-600">
|
||||
{formatCurrency(act.expectedDirectCost, currency)}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs font-mono text-slate-500">
|
||||
{formatCurrency(act.expectedIndirectCost, currency)}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs font-mono font-semibold text-slate-800">
|
||||
{formatCurrency(act.expectedTotalCost, currency)}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs font-mono text-slate-600">
|
||||
{formatPercent(act.percentOfTotal)}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs font-mono text-slate-500">
|
||||
{act.executionTimeMinutes > 0 ? formatMinutes(act.executionTimeMinutes) : '—'}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs font-mono text-slate-500">
|
||||
{formatPercent(act.executionProbability * 100)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
{/* Fila expandida con desglose de costos */}
|
||||
{isExpanded && (
|
||||
<TableRow key={`${act.activityId}-breakdown`} className="bg-slate-50/70 hover:bg-slate-50/70">
|
||||
<TableCell />
|
||||
<TableCell colSpan={7} className="py-2 pl-4 pr-6">
|
||||
<div className="space-y-1">
|
||||
{fixedCostExpected > 0 && (
|
||||
<div className="flex justify-between text-xs text-slate-500">
|
||||
<span>Fijo (insumos, licencias)</span>
|
||||
<span className="font-mono">{formatCurrency(fixedCostExpected, currency)}</span>
|
||||
</div>
|
||||
)}
|
||||
{act.resourceCostBreakdown.map((r) => (
|
||||
<div key={r.resourceId} className="flex justify-between text-xs text-slate-500">
|
||||
<span>{r.resourceName}</span>
|
||||
<span className="font-mono">{formatCurrency(r.cost, currency)}</span>
|
||||
</div>
|
||||
))}
|
||||
{act.resourceCostBreakdown.length === 0 && fixedCostExpected === 0 && (
|
||||
<p className="text-xs text-slate-400">Sin desglose disponible</p>
|
||||
)}
|
||||
{act.resourceCostBreakdown.length === 0 && fixedCostExpected > 0 && (
|
||||
<p className="text-xs text-slate-400 mt-0.5">Sin recursos asignados — costo es 100% fijo</p>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})}
|
||||
</TableBody>
|
||||
|
||||
@@ -30,6 +30,8 @@ export function CumulativeSavingsChart({
|
||||
position: 'insideEndTop',
|
||||
fontSize: 10,
|
||||
color: '#16A34A',
|
||||
padding: [0, 0, 0, 8],
|
||||
offset: [4, 0],
|
||||
},
|
||||
lineStyle: { color: '#16A34A', type: 'dashed', width: 2 },
|
||||
})
|
||||
|
||||
@@ -24,15 +24,15 @@ function classifyElement(type: string): BpmnElementCategory {
|
||||
// Emoji ⚡ como símbolo provisional — reemplazo por <Bot /> agendado Sprint 2-3.
|
||||
function makeBadgeHtml(): string {
|
||||
return `<div class="automatable-badge" title="Actividad marcada como automatizable" style="
|
||||
width:14px;height:14px;
|
||||
width:20px;height:20px;
|
||||
border-radius:50%;
|
||||
background:var(--inq-orange);
|
||||
border:1.5px solid white;
|
||||
box-shadow:0 1px 2px rgba(0,0,0,0.10);
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
color:white;
|
||||
font-size:8px;font-weight:700;
|
||||
line-height:14px;
|
||||
font-size:11px;font-weight:700;
|
||||
line-height:20px;
|
||||
cursor:default;
|
||||
pointer-events:none;
|
||||
z-index:10;
|
||||
|
||||
Reference in New Issue
Block a user