ML Engine Updates: - Updated BTCUSD with Polygon API data (2024-2025): 215,699 new records - Re-trained all ML models: Attention (R²: 0.223), Base, Metamodel (87.3% confidence) - Backtest results: +176.71R profit with aggressive_filter strategy Documentation Consolidation: - Created docs/99-analisis/_MAP.md index with 13 new analysis documents - Consolidated inventories: removed duplicates from orchestration/inventarios/ - Updated ML_INVENTORY.yml with BTCUSD metrics and training results - Added execution reports: FASE11-BTCUSD, correction issues, alignment validation Architecture & Integration: - Updated all module documentation with NEXUS v3.4 frontmatter - Fixed _MAP.md indexes across all folders - Updated orchestration plans and traces Files: 229 changed, 5064 insertions(+), 1872 deletions(-) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
21 KiB
21 KiB
| id | title | type | status | priority | epic | project | version | created_date | updated_date |
|---|---|---|---|---|---|---|---|---|---|
| ET-PAY-001 | Modelo de Datos Financial | Technical Specification | Done | Alta | OQI-005 | trading-platform | 1.0.0 | 2025-12-05 | 2026-01-04 |
ET-PAY-001: Modelo de Datos Financial
Epic: OQI-005 Pagos y Stripe Versión: 1.0 Fecha: 2025-12-05 Responsable: Requirements-Analyst
1. Descripción
Define el modelo de datos completo para el schema financial en PostgreSQL 15+, incluyendo:
- Pagos (one-time y recurrentes)
- Suscripciones
- Facturas
- Transacciones de wallet
- Reembolsos
- Métodos de pago
2. Arquitectura de Base de Datos
┌─────────────────────────────────────────────────────────────────┐
│ SCHEMA: financial │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌─────────────────┐ │
│ │ customers │◄────────│ payments │ │
│ └──────────────┘ └─────────────────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────────┐ │
│ │ │ refunds │ │
│ │ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ ┌─────────────────┐ │
│ │subscriptions │────────►│ invoices │ │
│ └──────────────┘ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │payment_methods│ │
│ └──────────────┘ │
│ │
│ ┌──────────────┐ │
│ │wallet_transactions │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
3. Especificación de Tablas
3.1 Tabla: customers
Datos de clientes en Stripe.
CREATE TABLE financial.customers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Relación con usuario
user_id UUID NOT NULL UNIQUE REFERENCES auth.users(id) ON DELETE CASCADE,
-- Stripe
stripe_customer_id VARCHAR(255) NOT NULL UNIQUE,
-- Información de facturación
email VARCHAR(255) NOT NULL,
name VARCHAR(255),
phone VARCHAR(50),
-- Dirección de facturación
billing_address JSONB, -- { "line1", "line2", "city", "state", "postal_code", "country" }
-- Configuración
default_payment_method_id UUID REFERENCES financial.payment_methods(id),
currency VARCHAR(3) DEFAULT 'usd',
-- Metadata
metadata JSONB,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
-- Constraints
CONSTRAINT chk_currency CHECK (currency IN ('usd', 'eur', 'gbp'))
);
-- Índices
CREATE INDEX idx_customers_user_id ON financial.customers(user_id);
CREATE INDEX idx_customers_stripe_customer_id ON financial.customers(stripe_customer_id);
CREATE INDEX idx_customers_email ON financial.customers(email);
-- Trigger
CREATE TRIGGER update_customers_updated_at BEFORE UPDATE ON financial.customers
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
3.2 Tabla: payment_methods
Métodos de pago guardados del cliente.
CREATE TABLE financial.payment_methods (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Relación
customer_id UUID NOT NULL REFERENCES financial.customers(id) ON DELETE CASCADE,
-- Stripe
stripe_payment_method_id VARCHAR(255) NOT NULL UNIQUE,
-- Tipo y detalles
type VARCHAR(50) NOT NULL, -- 'card', 'bank_account', 'paypal'
-- Para tarjetas
card_brand VARCHAR(50), -- 'visa', 'mastercard', 'amex'
card_last4 VARCHAR(4),
card_exp_month INTEGER,
card_exp_year INTEGER,
card_country VARCHAR(2),
-- Para cuentas bancarias
bank_name VARCHAR(255),
bank_last4 VARCHAR(4),
bank_account_type VARCHAR(20), -- 'checking', 'savings'
-- Estado
is_default BOOLEAN DEFAULT false,
is_active BOOLEAN DEFAULT true,
-- Metadata
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
-- Constraints
CONSTRAINT chk_payment_method_type CHECK (type IN ('card', 'bank_account', 'paypal'))
);
-- Índices
CREATE INDEX idx_payment_methods_customer_id ON financial.payment_methods(customer_id);
CREATE INDEX idx_payment_methods_stripe_id ON financial.payment_methods(stripe_payment_method_id);
CREATE INDEX idx_payment_methods_is_default ON financial.payment_methods(customer_id, is_default) WHERE is_default = true;
-- Trigger
CREATE TRIGGER update_payment_methods_updated_at BEFORE UPDATE ON financial.payment_methods
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
3.3 Tabla: payments
Registro de todos los pagos procesados.
CREATE TABLE financial.payments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Relaciones
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
customer_id UUID REFERENCES financial.customers(id),
-- Stripe
stripe_payment_intent_id VARCHAR(255) UNIQUE,
stripe_charge_id VARCHAR(255),
-- Tipo de pago
payment_type VARCHAR(50) NOT NULL, -- 'one_time', 'subscription', 'invoice', 'investment_deposit'
-- Montos
amount DECIMAL(15, 2) NOT NULL,
currency VARCHAR(3) NOT NULL DEFAULT 'usd',
amount_refunded DECIMAL(15, 2) DEFAULT 0.00,
net_amount DECIMAL(15, 2), -- amount - fees - refunds
-- Fees
application_fee DECIMAL(15, 2),
stripe_fee DECIMAL(15, 2),
-- Estado
status VARCHAR(50) NOT NULL DEFAULT 'pending', -- 'pending', 'processing', 'succeeded', 'failed', 'canceled', 'refunded'
-- Método de pago
payment_method_id UUID REFERENCES financial.payment_methods(id),
payment_method_type VARCHAR(50),
-- Referencias
subscription_id UUID REFERENCES financial.subscriptions(id),
invoice_id UUID REFERENCES financial.invoices(id),
-- Metadata
description TEXT,
metadata JSONB,
failure_code VARCHAR(100),
failure_message TEXT,
-- Timestamps
paid_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
-- Constraints
CONSTRAINT chk_amount_positive CHECK (amount > 0),
CONSTRAINT chk_payment_status CHECK (status IN ('pending', 'processing', 'succeeded', 'failed', 'canceled', 'refunded')),
CONSTRAINT chk_payment_type CHECK (payment_type IN ('one_time', 'subscription', 'invoice', 'investment_deposit'))
);
-- Índices
CREATE INDEX idx_payments_user_id ON financial.payments(user_id);
CREATE INDEX idx_payments_customer_id ON financial.payments(customer_id);
CREATE INDEX idx_payments_stripe_payment_intent ON financial.payments(stripe_payment_intent_id);
CREATE INDEX idx_payments_status ON financial.payments(status);
CREATE INDEX idx_payments_created_at ON financial.payments(created_at DESC);
CREATE INDEX idx_payments_subscription_id ON financial.payments(subscription_id);
CREATE INDEX idx_payments_invoice_id ON financial.payments(invoice_id);
-- Trigger
CREATE TRIGGER update_payments_updated_at BEFORE UPDATE ON financial.payments
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
3.4 Tabla: subscriptions
Suscripciones recurrentes de usuarios.
CREATE TABLE financial.subscriptions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Relaciones
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
customer_id UUID NOT NULL REFERENCES financial.customers(id),
-- Stripe
stripe_subscription_id VARCHAR(255) NOT NULL UNIQUE,
stripe_price_id VARCHAR(255) NOT NULL,
stripe_product_id VARCHAR(255) NOT NULL,
-- Plan
plan_name VARCHAR(100) NOT NULL, -- 'basic', 'pro', 'enterprise'
plan_interval VARCHAR(20) NOT NULL, -- 'month', 'year'
-- Precio
amount DECIMAL(15, 2) NOT NULL,
currency VARCHAR(3) NOT NULL DEFAULT 'usd',
-- Estado
status VARCHAR(50) NOT NULL DEFAULT 'active', -- 'active', 'past_due', 'canceled', 'unpaid', 'trialing'
-- Fechas del ciclo
current_period_start TIMESTAMP WITH TIME ZONE NOT NULL,
current_period_end TIMESTAMP WITH TIME ZONE NOT NULL,
-- Trial
trial_start TIMESTAMP WITH TIME ZONE,
trial_end TIMESTAMP WITH TIME ZONE,
-- Cancelación
cancel_at_period_end BOOLEAN DEFAULT false,
canceled_at TIMESTAMP WITH TIME ZONE,
cancellation_reason TEXT,
-- Metadata
metadata JSONB,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
-- Constraints
CONSTRAINT chk_subscription_status CHECK (status IN ('active', 'past_due', 'canceled', 'unpaid', 'trialing')),
CONSTRAINT chk_plan_interval CHECK (plan_interval IN ('month', 'year'))
);
-- Índices
CREATE INDEX idx_subscriptions_user_id ON financial.subscriptions(user_id);
CREATE INDEX idx_subscriptions_customer_id ON financial.subscriptions(customer_id);
CREATE INDEX idx_subscriptions_stripe_id ON financial.subscriptions(stripe_subscription_id);
CREATE INDEX idx_subscriptions_status ON financial.subscriptions(status);
CREATE INDEX idx_subscriptions_current_period_end ON financial.subscriptions(current_period_end);
-- Trigger
CREATE TRIGGER update_subscriptions_updated_at BEFORE UPDATE ON financial.subscriptions
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
3.5 Tabla: invoices
Facturas generadas para suscripciones y pagos.
CREATE TABLE financial.invoices (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Relaciones
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
customer_id UUID NOT NULL REFERENCES financial.customers(id),
subscription_id UUID REFERENCES financial.subscriptions(id),
-- Stripe
stripe_invoice_id VARCHAR(255) NOT NULL UNIQUE,
-- Número de factura
invoice_number VARCHAR(100),
-- Montos
subtotal DECIMAL(15, 2) NOT NULL,
tax DECIMAL(15, 2) DEFAULT 0.00,
discount DECIMAL(15, 2) DEFAULT 0.00,
total DECIMAL(15, 2) NOT NULL,
amount_paid DECIMAL(15, 2) DEFAULT 0.00,
amount_due DECIMAL(15, 2) NOT NULL,
currency VARCHAR(3) NOT NULL DEFAULT 'usd',
-- Estado
status VARCHAR(50) NOT NULL DEFAULT 'draft', -- 'draft', 'open', 'paid', 'void', 'uncollectible'
-- Fechas
due_date TIMESTAMP WITH TIME ZONE,
paid_at TIMESTAMP WITH TIME ZONE,
-- Items
line_items JSONB NOT NULL, -- Array de items de la factura
-- PDF
invoice_pdf_url TEXT,
hosted_invoice_url TEXT,
-- Metadata
metadata JSONB,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
-- Constraints
CONSTRAINT chk_invoice_status CHECK (status IN ('draft', 'open', 'paid', 'void', 'uncollectible'))
);
-- Índices
CREATE INDEX idx_invoices_user_id ON financial.invoices(user_id);
CREATE INDEX idx_invoices_customer_id ON financial.invoices(customer_id);
CREATE INDEX idx_invoices_subscription_id ON financial.invoices(subscription_id);
CREATE INDEX idx_invoices_stripe_id ON financial.invoices(stripe_invoice_id);
CREATE INDEX idx_invoices_status ON financial.invoices(status);
CREATE INDEX idx_invoices_created_at ON financial.invoices(created_at DESC);
-- Trigger
CREATE TRIGGER update_invoices_updated_at BEFORE UPDATE ON financial.invoices
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
3.6 Tabla: refunds
Registro de reembolsos procesados.
CREATE TABLE financial.refunds (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Relaciones
payment_id UUID NOT NULL REFERENCES financial.payments(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES auth.users(id),
-- Stripe
stripe_refund_id VARCHAR(255) NOT NULL UNIQUE,
-- Monto
amount DECIMAL(15, 2) NOT NULL,
currency VARCHAR(3) NOT NULL DEFAULT 'usd',
-- Razón
reason VARCHAR(50), -- 'duplicate', 'fraudulent', 'requested_by_customer'
description TEXT,
-- Estado
status VARCHAR(50) NOT NULL DEFAULT 'pending', -- 'pending', 'succeeded', 'failed', 'canceled'
-- Metadata
metadata JSONB,
processed_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
-- Constraints
CONSTRAINT chk_refund_amount_positive CHECK (amount > 0),
CONSTRAINT chk_refund_status CHECK (status IN ('pending', 'succeeded', 'failed', 'canceled')),
CONSTRAINT chk_refund_reason CHECK (reason IN ('duplicate', 'fraudulent', 'requested_by_customer', 'other'))
);
-- Índices
CREATE INDEX idx_refunds_payment_id ON financial.refunds(payment_id);
CREATE INDEX idx_refunds_user_id ON financial.refunds(user_id);
CREATE INDEX idx_refunds_stripe_id ON financial.refunds(stripe_refund_id);
CREATE INDEX idx_refunds_status ON financial.refunds(status);
3.7 Tabla: wallet_transactions
Transacciones de wallet interno (créditos, bonos).
CREATE TABLE financial.wallet_transactions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Relación
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
-- Tipo
type VARCHAR(50) NOT NULL, -- 'credit', 'debit', 'bonus', 'refund'
-- Monto
amount DECIMAL(15, 2) NOT NULL,
currency VARCHAR(3) NOT NULL DEFAULT 'usd',
-- Balance
balance_before DECIMAL(15, 2) NOT NULL,
balance_after DECIMAL(15, 2) NOT NULL,
-- Referencia
reference_type VARCHAR(50), -- 'payment', 'refund', 'admin_credit'
reference_id UUID,
-- Descripción
description TEXT,
-- Metadata
metadata JSONB,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
-- Constraints
CONSTRAINT chk_wallet_amount_positive CHECK (amount > 0),
CONSTRAINT chk_wallet_type CHECK (type IN ('credit', 'debit', 'bonus', 'refund'))
);
-- Índices
CREATE INDEX idx_wallet_transactions_user_id ON financial.wallet_transactions(user_id);
CREATE INDEX idx_wallet_transactions_type ON financial.wallet_transactions(type);
CREATE INDEX idx_wallet_transactions_created_at ON financial.wallet_transactions(created_at DESC);
4. Interfaces TypeScript
4.1 Types del Modelo
// src/types/financial.types.ts
export type PaymentStatus = 'pending' | 'processing' | 'succeeded' | 'failed' | 'canceled' | 'refunded';
export type PaymentType = 'one_time' | 'subscription' | 'invoice' | 'investment_deposit';
export type SubscriptionStatus = 'active' | 'past_due' | 'canceled' | 'unpaid' | 'trialing';
export type InvoiceStatus = 'draft' | 'open' | 'paid' | 'void' | 'uncollectible';
export type RefundReason = 'duplicate' | 'fraudulent' | 'requested_by_customer' | 'other';
export type WalletTransactionType = 'credit' | 'debit' | 'bonus' | 'refund';
export interface Customer {
id: string;
user_id: string;
stripe_customer_id: string;
email: string;
name: string | null;
phone: string | null;
billing_address: Record<string, any> | null;
default_payment_method_id: string | null;
currency: string;
metadata: Record<string, any> | null;
created_at: string;
updated_at: string;
}
export interface PaymentMethod {
id: string;
customer_id: string;
stripe_payment_method_id: string;
type: string;
card_brand: string | null;
card_last4: string | null;
card_exp_month: number | null;
card_exp_year: number | null;
card_country: string | null;
bank_name: string | null;
bank_last4: string | null;
bank_account_type: string | null;
is_default: boolean;
is_active: boolean;
created_at: string;
updated_at: string;
}
export interface Payment {
id: string;
user_id: string;
customer_id: string | null;
stripe_payment_intent_id: string | null;
stripe_charge_id: string | null;
payment_type: PaymentType;
amount: number;
currency: string;
amount_refunded: number;
net_amount: number | null;
application_fee: number | null;
stripe_fee: number | null;
status: PaymentStatus;
payment_method_id: string | null;
payment_method_type: string | null;
subscription_id: string | null;
invoice_id: string | null;
description: string | null;
metadata: Record<string, any> | null;
failure_code: string | null;
failure_message: string | null;
paid_at: string | null;
created_at: string;
updated_at: string;
}
export interface Subscription {
id: string;
user_id: string;
customer_id: string;
stripe_subscription_id: string;
stripe_price_id: string;
stripe_product_id: string;
plan_name: string;
plan_interval: string;
amount: number;
currency: string;
status: SubscriptionStatus;
current_period_start: string;
current_period_end: string;
trial_start: string | null;
trial_end: string | null;
cancel_at_period_end: boolean;
canceled_at: string | null;
cancellation_reason: string | null;
metadata: Record<string, any> | null;
created_at: string;
updated_at: string;
}
export interface Invoice {
id: string;
user_id: string;
customer_id: string;
subscription_id: string | null;
stripe_invoice_id: string;
invoice_number: string | null;
subtotal: number;
tax: number;
discount: number;
total: number;
amount_paid: number;
amount_due: number;
currency: string;
status: InvoiceStatus;
due_date: string | null;
paid_at: string | null;
line_items: Record<string, any>[];
invoice_pdf_url: string | null;
hosted_invoice_url: string | null;
metadata: Record<string, any> | null;
created_at: string;
updated_at: string;
}
export interface Refund {
id: string;
payment_id: string;
user_id: string;
stripe_refund_id: string;
amount: number;
currency: string;
reason: RefundReason | null;
description: string | null;
status: PaymentStatus;
metadata: Record<string, any> | null;
processed_at: string | null;
created_at: string;
}
export interface WalletTransaction {
id: string;
user_id: string;
type: WalletTransactionType;
amount: number;
currency: string;
balance_before: number;
balance_after: number;
reference_type: string | null;
reference_id: string | null;
description: string | null;
metadata: Record<string, any> | null;
created_at: string;
}
5. Views Útiles
5.1 Vista: Payment Summary por Usuario
CREATE VIEW financial.user_payment_summary AS
SELECT
user_id,
COUNT(*) as total_payments,
SUM(CASE WHEN status = 'succeeded' THEN 1 ELSE 0 END) as successful_payments,
SUM(CASE WHEN status = 'succeeded' THEN amount ELSE 0 END) as total_amount_paid,
MAX(created_at) as last_payment_date
FROM financial.payments
GROUP BY user_id;
6. Configuración
6.1 Variables de Entorno
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/trading
# Financial Schema
FINANCIAL_DEFAULT_CURRENCY=usd
7. Referencias
- Stripe API Objects Documentation
- PostgreSQL JSONB Best Practices
- Payment Gateway Database Design