Catálogo de Herramientas del Asesor de IA
🧰 Catálogo de Herramientas del Asesor de IA
Sección titulada «🧰 Catálogo de Herramientas del Asesor de IA»taxAnalyst define veinticinco herramientas de solo lectura (ADVISOR_TOOL_NAMES); cuáles ve una ejecución depende de su perfil de ejecución (ver Niveles por perfil de ejecución). Las entradas son objetos Zod estrictos: los campos desconocidos se rechazan. Los símbolos se pasan a mayúsculas y luego deben cumplir ^[A-Z0-9.]{1,20}$. Ninguna herramienta acepta un id de cuenta; todas leen todas las cuentas a la vez, salvo account_holdings, que recibe el nombre de una cuenta (recortado, de 1 a 64 caracteres) y lo resuelve en el servidor. Un modelo que envíe accountId sigue fallando con INVALID_TOOL_INPUT. Ninguna herramienta acepta una cantidad ni una moneda. Las cifras fiscales son por activo entre cuentas en cualquier caso, coherente con FIFO Global vs Custodia.
Todas las herramientas devuelven una unión discriminada: { kind: 'ok', payload } o { kind: 'truncated', maxChars, actualChars } (ver presupuestos de resultados).
🪜 Niveles por Perfil de Ejecución
Sección titulada «🪜 Niveles por Perfil de Ejecución»Cada herramienta pertenece a un nivel en ADVISOR_TOOL_TIERS (shared-types), comprobado con satisfies para que no se pueda añadir una herramienta sin nivel. La selección es código estático, no un ajuste: selectExposedTools en ai/tools/toolExposure.ts la aplica al construir el mapa de herramientas de taxAnalyst.
| Perfil | Herramientas expuestas |
|---|---|
local |
las 14 herramientas core |
metered, mixed |
las 25 herramientas |
- core (14):
portfolio_summary,holding_detail,account_holdings,kpis,asset_allocation,spanish_tax_report,tax_year_comparison,fiscal_integrity,data_gaps,token_history,live_prices,scenario_position_value,breakeven_price,explain_metric. - extended (11):
fiscal_integrity_rows,token_lots,risk_metrics,drawdown_curve,performance_history,volatility_heatmap,derivatives_pnl,custody_locations,scenario_portfolio_shock,concentration_risk,tx_search.
Los modelos locales pequeños eligen de forma medible peor con una lista larga de herramientas, por eso una ejecución local solo ve el nivel core. Una cadena mixta se resuelve al perfil metered y por tanto expone todas las herramientas. Las instrucciones del agente listan exactamente las herramientas que expone la ejecución. Ver perfiles de ejecución.
📚 Catálogo
Sección titulada «📚 Catálogo»| Herramienta | Envuelve | Entrada | Payload (resumen) |
|---|---|---|---|
portfolio_summary |
GetPortfolioSummaryUseCase |
ninguna | metrics (totales, ratesIncomplete, pricesIncomplete, currency), posiciones ranked (las topNHoldings primeras por valor), omittedCount, unvalued, unvaluedCount. Moneda base. |
fiscal_integrity |
GetFiscalIntegrityUseCase |
ninguna | Como máximo 10 groups (qualityFlag, severity, count, pendingReview) ordenados por count, más totalDefects, pendingReview, needsRecalculation. Nunca filas. |
fiscal_integrity_rows |
GetFiscalIntegrityUseCase |
qualityFlag, page (base cero, por defecto 0) |
Una página de filas a nivel de transacción (rowsPageSize) con metadatos de paginación. |
token_history |
GetTokenHistoryUseCase |
symbol |
Hasta lotsPageSize lotes (id, fecha, exchange, cantidades, costes, estado, historyCount, relocationCount) y omittedCount. |
token_lots |
GetTokenHistoryUseCase |
symbol, page (por defecto 0) |
Una página de la lista completa de lotes (lotsPageSize) con metadatos de paginación. |
asset_allocation |
GetAssetAllocationUseCase |
ninguna | Elementos valorados ranked (amount, valueFiat, allocationPct, currency), omittedCount, elementos unvalued (solo amount). Moneda base. |
risk_metrics |
GetRiskMetricsUseCase |
ninguna | maxDrawdownPct, annualizedVolatility, sharpeRatio, alpha, beta. Ratios, sin campo de moneda. |
kpis |
GetKpisUseCase |
ninguna | Equity, coste base, PnL, máximo histórico, drawdown, volatilidad, Sharpe, currency, flags de incompletitud y, opcionalmente, estadísticas de operaciones y mejor/peor activo. Moneda base. |
drawdown_curve |
GetDrawdownCurveUseCase |
days? (entero 1 a 3650) |
points de date y drawdownPct, submuestreados a un máximo de 120, más omittedCount. |
performance_history |
GetPerformanceHistoryUseCase |
days? (1 a 3650) |
currency (la moneda base), points (portfolioValue, nullable; btcValue opcional; drawdownPct) submuestreados a un máximo de 120, omittedCount. |
volatility_heatmap |
GetVolatilityHeatmapUseCase |
year? (entero) |
cells de date y volatility, submuestreadas a un máximo de 120, omittedCount. |
spanish_tax_report |
GetSpanishTaxReportUseCase |
year (entero, mínimo 2009), method? |
summary del IRPF, contadores de no convertibles y excluidos, auditTrail limitado a 20 filas, omittedCount. Siempre EUR. |
live_prices |
IPriceHistoryPort.getLatest |
symbols (1 a 25) |
prices (price, currency, timestamp, provider, stalenessSeconds) y notTracked. |
holding_detail |
GetPortfolioSummaryUseCase |
symbol |
Uno de valued (posición con valor), unvalued (solo cantidad y coste base) o not_held. Encuentra una posición fuera del top-N de portfolio_summary. Moneda base. |
account_holdings |
ListAccountsUseCase, GetPortfolioSummaryUseCase |
accountName (recortado, 1 a 64) |
resolved con una sección por cuenta del árbol de la cuenta resuelta (sus propios metrics, ranked, omittedCount, unvalued) más emptyAccountCount; o ambiguous (nombres candidatos) o not_found (nombres de nivel superior). Primero coincidencia exacta sin distinguir mayúsculas; un prefijo solo si ninguna coincide exactamente. Nunca suma entre cuentas. |
tax_year_comparison |
GetSpanishTaxReportUseCase (dos veces) |
yearA, yearB (distintos, mínimo 2009), method? |
Por año summary, unconvertibleCount, excludedFlaggedEvents, excludedUnresolvedIncomeCount, completeness; deltas (segundo año menos primero) calculados por compareTaxSummaries, cada uno un delta o delta_incomparable si alguno de los años está incompleto. EUR, sin pista de auditoría. |
data_gaps |
GetPortfolioSummaryUseCase, GetFiscalIntegrityUseCase |
ninguna | unvalued dividido en no_price y no_rate (solo símbolo y cantidad), integrity (como máximo 10 grupos, totalDefects, needsRecalculation) y una lista fija nextTools. |
explain_metric |
tabla estática | metric (enum cerrado MetricId) |
La definición en el locale de la petición y producedBy, la herramienta que informa la métrica. El texto expresa las fórmulas con palabras, sin cifras de ejemplo. |
scenario_position_value |
GetPortfolioScenarioUseCase |
symbol, hypotheticalPrice |
computed (positionValue, deltaVsCurrent, impliedAllocationPct), not_held, unvalued (solo valor de la posición) o empty_portfolio; cada uno con currency. La cantidad siempre sale del libro. |
breakeven_price |
GetPortfolioScenarioUseCase |
symbol |
computed (avgUnitCost), not_held, unconvertible_cost_basis o zero_quantity. Usa el coste base de las posiciones, que puede diferir de kpis. |
derivatives_pnl |
GetDerivativesPnlUseCase |
ninguna | Contratos ordenados por PnL realizado absoluto y limitados a topNHoldings (realizedPnl, funding, fees, netPnl, currency), omittedCount. |
custody_locations |
GetLotCustodyLocationsUseCase |
symbol? |
Cantidad por cuenta no sintética y activo (lotCount), limitada a topNHoldings, omittedCount y syntheticRowCount para las filas ownwallet-* que no lista. Lee solo el libro de custodia; la ubicación no afecta a la tributación. |
scenario_portfolio_shock |
GetPortfolioScenarioUseCase |
shock: { kind: 'uniform', pct } o { kind: 'per_asset', shocks } (1 a 25 entradas); pct de -100 a 1000 |
computed (before y after por activo limitados a topNHoldings, omittedCount, totalBefore, totalAfter, delta, unvaluedSymbols, notHeld) o empty_portfolio. En un shock por activo, los activos no listados no se mueven. |
concentration_risk |
GetPortfolioScenarioUseCase |
ninguna | Bloques all y excludingStablecoins (computed con top1Weight, top3Weight, hhi en escala de cero a uno, effectiveHoldings, o empty), stablecoinWeight (null si nada está valorado) y unvaluedCount. |
tx_search |
SearchSpotTransactionsUseCase |
symbol?, from?, to? (días inclusivos), types? (no vacío), page (por defecto 1) |
Una página (rowsPageSize) de filas (date, type, activos e importes, priceFiat, totalFiat, fiatCurrency, fee como NONE o CHARGED, exchange, edited), la más reciente primero, con page, pageSize, totalPages, totalCount. |
Envoltorios compartidos: fiscal_integrity, fiscal_integrity_rows y (en su mitad de integridad) data_gaps usan GetFiscalIntegrityUseCase; token_history y token_lots usan GetTokenHistoryUseCase; portfolio_summary, holding_detail, account_holdings y data_gaps leen GetPortfolioSummaryUseCase; las cuatro herramientas de escenarios (scenario_position_value, breakeven_price, scenario_portfolio_shock, concentration_risk) comparten GetPortfolioScenarioUseCase; spanish_tax_report y tax_year_comparison comparten GetSpanishTaxReportUseCase.
Ejemplo de resultado de portfolio_summary tal como lo ve el modelo (solo la forma; las cifras son ilustrativas):
{ "kind": "ok", "payload": { "metrics": { "ratesIncomplete": false, "pricesIncomplete": true, "totalEquityFiat": "18250.40", "totalCostBasisFiat": "12000.00", "totalRealizedPnlFiat": "930.10", "totalUnrealizedPnlFiat": "6250.40", "totalPnlFiat": "7180.50", "currency": "USD" }, "ranked": [ { "id": "btc", "symbol": "BTC", "amount": "0.25", "avgPriceFiat": "40000", "costBasisFiat": "10000", "currentValueFiat": "15000", "unrealizedPnlFiat": "5000", "currency": "USD" } ], "omittedCount": 0, "unvalued": [], "unvaluedCount": 0 }}Los fallos de herramienta aparecen en el stream como tool-error con INVALID_TOOL_INPUT (error de validación de Mastra sobre la entrada) o USE_CASE_FAILED (el caso de uso envuelto lanzó una excepción).
💱 Reglas de Moneda
Sección titulada «💱 Reglas de Moneda»portfolio_summary,kpis,asset_allocationylive_pricesrespetan la moneda base del usuario, leída en el servidor desde los ajustes e inyectada mediante el contexto de la petición. El modelo nunca proporciona una moneda.asset_allocationconvierte la valoración diaria canónica en EUR al tipo de cambio de la fecha de valoración (VALUATION_CONVERTED, igual quekpis). Una posición sin serie de precios, o sin tipo de cambio, quedaunvalued: informa su cantidad y se excluye de todo porcentaje, nunca se deja pasar con factor uno.risk_metrics,drawdown_curveyvolatility_heatmapdevuelven ratios, porcentajes o estadísticas sin moneda.spanish_tax_reportytax_year_comparisonson siempre EUR, como exige el IRPF.holding_detail,account_holdings,data_gaps, las cuatro herramientas de escenarios yderivatives_pnlleen la moneda base del contexto de la petición. Los resultados de escenarios llevan un campocurrency.custody_locationsdevuelve cantidades y cada fila detx_searchlleva su propiofiatCurrency.performance_historyrespeta la moneda base: cada punto se convierte con la última tasaEUR/<base>almacenada en la fecha del propio punto o antes, igual quekpis, y el campocurrencydel resultado la indica. Un punto que ninguna tasa cubre tieneportfolioValue: null, nunca un valor en EUR ni0.drawdownPctes una razón y se sigue derivando de la serie en EUR, por lo que no depende de la moneda base.
Al agente se le indica que exprese la moneda declarada de cada resultado junto a la cifra y que nunca convierta ni reetiquete.
📄 Paginación y Garantía de Precios en Vivo
Sección titulada «📄 Paginación y Garantía de Precios en Vivo»fiscal_integritynunca devuelve filas. Las filas a nivel de transacción solo son accesibles mediantefiscal_integrity_rows.token_historydevuelve una página de lotes, no un top 20 fijo; la lista completa es accesible mediantetoken_lots. Ambas están sujetas a los mismos presupuestos de caracteres, y una página que aún excede su presupuesto se informa comotruncated.- Las páginas de
tx_searchempiezan en 1 (por defecto) y van de la más reciente a la más antigua. Una página más allá del final es un resultadookvacío con los totales reales, y los filtros de fecha y tipo se ejecutan sobre los valores editados: la fila previa a la edición nunca se devuelve. live_priceslee el último precio ya en caché por símbolo desde el puerto de historial de precios (la misma caché que llena el stream de mercado). No abre ninguna conexión con proveedores ni inicia ningún stream. Un símbolo sin precio en caché aparece ennotTracked.
🧮 La Aritmética Vive en core-domain
Sección titulada «🧮 La Aritmética Vive en core-domain»Una cifra de escenario la produce una función pura de packages/core-domain: positionValueAt, breakevenPrice, applyShock y concentrationOf en portfolioScenarios.ts, además de compareTaxSummaries y summarizeCustodyLocations. Todas pasan por Money, y toda división por una variable está protegida por una comprobación isZero que devuelve un resultado tipado en lugar de lanzar una excepción. El subárbol de IA no importa ni Money ni decimal.js, y un escaneo del código fuente lo hace cumplir. Al agente se le indica que nunca calcule, convierta, sume, reste ni estime una cifra, y que exprese tal cual un resultado no calculado (no poseído, ambiguo, coste base no convertible).
Las stablecoins de concentration_risk salen de la lista cerrada STABLECOIN_SYMBOLS de shared-types (USDT, USDC, DAI, EURC, FDUSD, PYUSD, TUSD, USDE); un símbolo que no figura en ella cuenta como arriesgado. La lista es solo clasificación de visualización y ningún código fiscal ni de FIFO la lee.
🚫 Herramientas Dejadas Fuera a Propósito: fees_paid y cash_flow_summary
Sección titulada «🚫 Herramientas Dejadas Fuera a Propósito: fees_paid y cash_flow_summary»Ambas se midieron contra exportaciones reales de exchanges y no superaron el filtro, por lo que no están en el catálogo. Solo las comisiones de Bitvavo son nativamente EUR y están completas; cualquier otra fuente cobra en cripto o USD, lo que exigiría un precio fechado por cada activo de comisión (lógica analítica nueva, no un envoltorio). Una cifra global de entradas y salidas de fiat contaría como retirada la transferencia interna spottofutures de Kraken spot, y tres fuentes no exportan movimientos de fiat. Se descartó publicarlas por fuente porque un total que cubre en silencio solo algunas fuentes rompe la regla de que “toda cifra declara su completitud”.
Volverán en un seguimiento cuando el subtipo spottofutures de Kraken se declare transferencia interna en profiles.ts, exista la valoración en EUR del activo de comisión como caso de uso y las comisiones con cero explícito sobrevivan a la normalización en todos los perfiles.
🔢 Ordenación Solo de Visualización
Sección titulada «🔢 Ordenación Solo de Visualización»La capa de IA no contiene ningún .sort( propio. Helpers puros de packages/core-domain aportan todo orden: rankHoldingsByValue y rankByAbsoluteValue (monetarios, comparados mediante Money), orderByCountDescending, orderByIsoDateDescending y orderByIsoDateDescendingThenKey (claves enteras y de fecha ISO; la última con desempate por clave, usada por tx_search), además de downsampleSeries (paso uniforme, máximo 120 puntos). Ninguno tiene acceso a un lote, una cola o una cuenta, de modo que la separación entre FIFO fiscal y custodia queda intacta.