ApisDom Studio: Plataforma SaaS con Next.js 16, FastAPI y Claude Sonnet
Plataforma SaaS de generación de contenido con IA. Claude Sonnet 4.5 + Haiku 4.5, streaming SSE en vivo, editor TipTap, 15 tipos de contenido, adaptación a 7 redes sociales y checkout Stripe.

Galería del Proyecto











Tecnologías Utilizadas
Sobre el Proyecto
¿Que es APISDOM Studio?
APISDOM Studio es una plataforma SaaS de generacion de contenido con IA que convierte un brief simple (tema, tono, audiencia, longitud) en un articulo profesional listo para publicar: HTML/Markdown estructurado, metadata SEO, resumen, tabla de contenidos, Q&A, bloques de codigo y fuentes web citadas. Cada generacion cruza la red en tiempo real por Server-Sent Events para que el usuario vea el texto aparecer palabra a palabra en el editor.
El sistema se divide en dos servicios independientes con fronteras explicitas:
| Servicio | Responsabilidad | Hosting |
|---|---|---|
Frontend Next.js (studio.apisdom.com) | UI, autenticacion, panel admin, editor TipTap, checkout Stripe, gestion de usuarios/creditos/perfiles/marcas, proxy firmado al backend | Firebase App Hosting (Cloud Run us-central1, 1 CPU, 512 MiB, escala 0-10) |
Backend FastAPI (studio-api-...europe-west1.run.app) | Motor de generacion, llamadas a Claude, busqueda web, streaming SSE, perfiles de generacion, historial, adaptacion a redes sociales | Cloud Run europe-west1, escala a cero |
El motor usa dos modelos de Claude segun el tier del usuario: claude-haiku-4-5 para tier free y adaptacion a redes, claude-sonnet-4-5 para tier premium. El modelo puede sobrescribirse en vivo desde el panel de administracion sin redesplegar (override con TTL de 60s en AdminConfig, y una clave emergency_model_override que fuerza el modelo para todos los usuarios).
Que puede generar
15 tipos de contenido con 4 longitudes, 10 tonos, 4 niveles de audiencia y 2 idiomas (ES / EN), configurables por llamada o fijados en un perfil reutilizable.
| Categoria | Tipos soportados |
|---|---|
| Articulos | article · blog_post · tutorial · how_to · case_study · comparison · listicle · opinion |
| Marketing | landing_page · product_description · press_release · newsletter |
| Otros | social_post · documentation · faq |
Longitudes reales (definidas en schemas.py y mapeadas a max_tokens en engine.py):
| Length | Palabras objetivo | max_tokens |
|---|---|---|
short | 300-500 | 2.000 |
medium | 800-1.200 | 4.000 |
long | 1.800-2.500 | 8.000 |
extra_long | 3.000-5.000 | 16.000 |
Cada generacion devuelve un JSON estructurado con los mismos campos siempre: content_body, meta_seo (title, description, slug, Open Graph, keywords), summary, table_of_contents, sections, qa_items, code_blocks, sources, input_tokens, output_tokens, web_searches y model. Los campos opcionales (Q&A, codigo, TOC, resumen, fuentes) se activan por parametro en el request y se validan antes de llegar a Claude.
Arquitectura hexagonal (Ports & Adapters) de verdad
El frontend esta organizado en capas con una regla dura verificada en CI: core/ no puede importar de adapters/, lib/, ni paquetes externos. La estructura real de src/ es:
src/
├── core/
│ ├── entities/ (14 entidades: User, Generation, ApiKey, Partner, Brand, ...)
│ ├── ports/ (13 interfaces: AuthPort, ContentPort, PaymentPort, ...)
│ └── use-cases/ (18 funciones puras: CalculateCreditCost, ValidateGenerationInput, ...)
├── adapters/
│ ├── api/ (StudioApiAdapter — cliente del backend FastAPI)
│ ├── auth/ (AuthAdapter — Firebase Auth)
│ ├── database/ (firebase/, memory/)
│ └── payment/ (StripePaymentAdapter)
├── presentation/
│ ├── components/ (169 componentes .tsx organizados por feature)
│ ├── hooks/ (35 hooks)
│ └── stores/ (Zustand)
├── app/ (Next.js App Router — 28 paginas bajo [locale])
├── config/ (pricing, credit-costs, env, firebase, i18n)
└── lib/
Cifras reales: 458 archivos .ts/.tsx, 169 componentes React, 53 route handlers Next.js (/api/**/route.ts), 28 paginas bajo [locale], i18n completo en es / en.
Stack tecnico real
Frontend (frontend/package.json)
| Pieza | Version |
|---|---|
| Next.js | 16.1.5 (App Router, Server Components, SSR) |
| React | 19.0.4 |
| TypeScript | 5.9.3 |
| Tailwind CSS | 4.1.18 + @tailwindcss/typography |
| TipTap | 3.20.0 (StarterKit + image, link, table, text-align, code-block-lowlight, placeholder) |
| Framer Motion | 12.29.2 |
| Firebase client SDK | 12.8.0 |
| Firebase Admin SDK | 13.6.0 |
| Stripe | @stripe/stripe-js 8.6.4 + stripe 20.2.0 server SDK |
| Zustand | 5.0.10 (state management) |
| Zod | 4.3.6 (validacion runtime) |
| isomorphic-dompurify | 2.35.0 (sanitizacion HTML) |
| marked | 17.0.3 (Markdown → HTML en descargas) |
| jsPDF | 4.0.0 (descargas PDF) |
| nodemailer + Zoho SMTP | Newsletter y emails transaccionales |
| Phosphor Icons | 2.1.7 (unica libreria de iconos) |
| Vitest | 4.0.18 + Playwright 1.58.0 |
Backend (backend/requirements.txt)
| Pieza | Version minima |
|---|---|
| Python | 3.12+ |
| FastAPI | 0.109+ |
| Uvicorn | 0.27+ |
| Anthropic SDK | 0.44+ (claude-sonnet-4-5-20250929 · claude-haiku-4-5-20251001) |
| SQLAlchemy async | 2.0.25+ |
| aiosqlite | 0.19+ (desarrollo) · PostgreSQL asyncpg en produccion via DATABASE_URL |
| Pydantic | 2.6+ + pydantic-settings |
| sse-starlette | 2.0+ (Server-Sent Events) |
| httpx | 0.26+ |
| mypy --strict | 1.8+ |
| ruff | 0.2+ (E,W,F,B,C90,I,N,UP,S) |
Hosting y dominio
- Frontend: Firebase App Hosting — proyecto
apisdom-studio, bucketapisdom-studio.firebasestorage.app, dominiohttps://studio.apisdom.com. - Backend: Google Cloud Run region
europe-west1, despliegue automatico por Cloud Build desde push amain(imagen en Artifact Registryeurope-west1-docker.pkg.dev). - Pagos: Stripe live (
pk_live_...). - Email transaccional: Zoho SMTP via nodemailer.
Información Técnica
apisdom-studio-saas-generacion-contenido-ia
rNvCnikb3w4OZZb8gA4r
Documentación Específica del Proyecto
Pipeline de generacion de contenido
Una llamada a POST /v1/generate lanza un flujo secuencial de 8 pasos que parte del brief del usuario y termina con un JSON completo persistido en historial. El frontend actua como proxy firmado hacia el backend FastAPI para que la clave del backend no toque nunca el navegador.
| Paso | Proceso | Tecnologia |
|---|---|---|
| 1. Autenticacion y creditos | /api/internal/generate/route.ts verifica Firebase Auth, calcula coste con CalculateCreditCost (tipo + longitud), bloquea si no hay creditos y descuenta atomicamente | Firebase Admin SDK + Firestore |
| 2. Validacion de request | validate_generate_request + validate_topic_length rechazan combinaciones invalidas (include_code sin code_language, max_qa_items fuera de rango, topic vacio...) antes de llamar al modelo | Pydantic v2 + validadores custom |
| 3. Seleccion de modelo | _get_model_name_async lee DB overrides con prioridad: emergency_model_override → model_<tier> → env var por defecto. TTL de cache 60s en AdminConfigRepository | SQLAlchemy async + cache |
| 4. Construccion del prompt | build_system_prompt inyecta skill segun content_type, reglas de longitud, tono, audiencia, idioma, website del usuario, instrucciones custom y flags de Q&A / codigo / TOC / fuentes | Plantillas en prompts/seo-content-generator/ |
| 5. Web search condicional | should_enable_search + determine_search_config deciden si activar la tool nativa web_search_20250305 de Anthropic con tool_choice=any y max_sources configurables | Anthropic web_search tool |
| 6. Llamada a Claude con fallback | Primer intento al modelo del tier. Si devuelve RateLimitError o APIStatusError 429/503, reintento automatico con el modelo alternativo sin perder el request | Anthropic SDK |
| 7. Procesado de respuesta | Extraccion de tool_use_blocks, parseo del JSON estructurado, extraccion de citations nativas (web_search_result_location) con URL, titulo y texto citado | Parsers propios |
| 8. Persistencia + respuesta | Guardado en historial (Generation SQLAlchemy) con input_tokens, output_tokens, web_searches, model y status. Respuesta final como JSON o stream SSE segun stream=true/false | SQLAlchemy + sse-starlette |
Streaming SSE en tiempo real
Cuando el request trae stream: true, el backend devuelve StreamingResponse con text/event-stream y el frontend consume con EventSource. Cada evento lleva tipo y payload:
| Evento | Contenido |
|---|---|
meta | Modelo elegido, max_tokens, si hay web search activa |
delta | Fragmento de texto generado (palabra / frase) — se pinta en vivo en el editor TipTap |
tool_use | Claude lanzo una busqueda web (web_search tool call) |
citation | URL + titulo + texto citado — aparece en el panel de fuentes mientras se genera |
done | JSON final completo con todos los campos estructurados |
error | Error capturado por wrap_sse_stream — se convierte en stream_error sin cortar la conexion bruscamente |
El handler esta en streaming.py y el usuario ve el contenido aparecer progresivamente sin esperar a que Claude termine.
Pipeline de adaptacion a redes sociales (POST /v1/adapt)
Una vez generado un contenido, el usuario puede adaptarlo a 7 plataformas sociales sin coste extra para adaptaciones de 1 a 3 plataformas (1 credito) o 4 a 7 plataformas (2 creditos), definido en credit-costs.ts.
| Paso | Proceso |
|---|---|
| 1. Lectura del contenido origen | Se pasan content_body y metadata del contenido generado previamente |
| 2. Limites nativos por plataforma | GET /v1/platforms devuelve el limite real de caracteres de Facebook, Instagram, X, LinkedIn, TikTok, YouTube, Pinterest |
| 3. Llamada a Claude Haiku | Se usa claude-haiku-4-5 (anthropic_model_adapt) para reducir coste — la adaptacion no necesita razonamiento largo |
| 4. Formato nativo | Hashtags, menciones, emojis, CTAs y saltos de linea segun las convenciones de cada plataforma |
| 5. Respuesta multi-plataforma | JSON con una entrada por plataforma solicitada, cada una con text, character_count, hashtags y avisos de limite |
Las adaptaciones no consumen web_search y no generan nuevas citations — trabajan sobre el contenido ya existente.
Perfiles de generacion reutilizables
Los usuarios (y el panel admin) pueden guardar profiles con configuracion preferida de tono, longitud, audiencia, idioma, style_guide, brand_voice y preferencias SEO. Cada request puede referenciar un perfil por profile_id y el backend los mergea con los parametros del request antes de construir el prompt. Endpoints en routes_profiles.py y UI en app/[locale]/profiles/.
Cada generacion queda registrada en el historial del usuario con todos los parametros, tokens consumidos, fuentes citadas y el JSON completo del resultado. El historial se expone en app/[locale]/history/ con filtros, re-descarga y re-uso como plantilla.
Editor TipTap y panel de administracion
APISDOM Studio tiene dos piezas de UI centrales: el editor WYSIWYG con TipTap donde el usuario ve y retoca el contenido generado por Claude, y un panel de administracion completo donde se gestionan usuarios, creditos, modelos, blog, partners y metricas globales.
Editor WYSIWYG — TipTap 3.20
El editor usa TipTap 3.20 con un conjunto completo de extensiones oficiales instaladas en produccion: StarterKit, Image, Link, Placeholder, Table + TableCell / TableHeader / TableRow, TextAlign y CodeBlockLowlight con resaltado de sintaxis via lowlight 3.3. Implementacion en editor/:
EditorContentArea.tsx— Area de edicion principal conuseEditor, recibe el stream SSE del backend y va insertando nodos en vivo mientras Claude generaEditorToolbar.tsx— Barra de formato: bold / italic / strike / headings / listas / quote / codigo / link / imagen / tabla / alineacion / undo-redoEditorSidebar.tsx— Panel lateral con metadata SEO, TOC navegable, lista de fuentes citadas, Q&A y bloques de codigo detectadosFreeEditorSidebar.tsx— Variante para el modo editor libre sin generacion previaHtmlSourceModal.tsx— Modal para editar el HTML crudo (sanitizado conisomorphic-dompurifyantes de aplicarse)EditorActions.tsx— Exportar a PDF (jsPDF), Markdown (marked), HTML plano, copiar al portapapeles, guardar como borrador
El contenido generado llega del backend en formato html / markdown / plain_text segun output_format del request, se parsea al schema de TipTap y el usuario puede seguir editando libremente. Todo lo que sale del editor pasa por isomorphic-dompurify antes de persistirse para evitar XSS.
Panel de administracion
Ruta /[locale]/admin protegida por rol de admin verificado en Firebase custom claims. La UI esta organizada en tabs en admin/:
| Tab | Componente | Que permite |
|---|---|---|
| Estadisticas globales | GlobalStatsTab.tsx | Usuarios totales, generaciones hoy/semana/mes, tokens gastados, creditos vendidos, modelo mas usado |
| Usuarios de la app | AppUsersTab.tsx + UserDetailModal.tsx + AddCreditsModal.tsx | Buscar usuarios, ver detalle, anadir/quitar creditos manualmente, bloquear/desbloquear, ver historial de generaciones del usuario |
| Usuarios API | ApiUsersTab.tsx | Gestion de usuarios que consumen la API publica /v1/** via API key |
| Logs de API | ApiLogsTab.tsx | Registro de llamadas a la API con usuario, endpoint, status, tokens, latencia |
| Config del backend | BackendConfigTab.tsx | Override en vivo (sin redesplegar) de modelo por tier, emergency_model_override, max_tokens_<length>, cache TTL 60s |
| Blog | BlogTab.tsx + BlogEditorModal.tsx + BlogEditorGuideModal.tsx | CRUD del blog publico en /blog con el mismo editor TipTap |
| Partners | partners/PartnersTab.tsx + 9 componentes | White-label completo: crear partners, planes, usuarios por partner, analytics, settings |
El modelo activo y los max_tokens por longitud se pueden cambiar en caliente desde BackendConfigTab sin tocar env vars ni redesplegar — el backend lee el override con TTL de 60 segundos en AdminConfigRepository.
Partners / white-label
El sistema soporta partners con endpoints publicos bajo /api/v1/[partner]/** (generate, adapt, user/status) para integraciones de terceros con su propia base de usuarios, planes y analytics. Gestionados en el panel admin via 9 componentes dedicados: PartnerDashboard, PartnerDetail, PartnerPlans, PartnerUsers, PartnerSettings, PartnerGuideModal, CreatePartnerModal, PlanEditorModal, PartnersTab.
Cada partner tiene sus propios planes (creditos + precio), sus propios usuarios aislados y endpoints dedicados con autenticacion por API key. La separacion esta en la entidad Partner en core/entities/Partner.ts y PartnerPort en core/ports/PartnerPort.ts.
API publica para desarrolladores
Aparte de la app web, el backend expone una API REST publica documentada con OpenAPI 3 (/docs Swagger, /redoc ReDoc con logo custom inyectado en el schema). Los endpoints publicos son:
| Metodo | Endpoint | Auth | Descripcion |
|---|---|---|---|
POST | /v1/generate | Bearer | Generar contenido (standard o SSE) |
POST | /v1/adapt | Bearer | Adaptar a redes sociales |
GET | /v1/platforms | — | Listar plataformas y limites |
GET / POST / PUT / DELETE | /v1/profiles/** | Bearer | CRUD de perfiles |
La pagina publica para desarrolladores esta en /[locale]/developers, la gestion de API keys del usuario en /[locale]/api-keys y la entidad + use-case de validacion en ApiKey.ts + ValidateApiKeyInput.ts.
Microservicio FastAPI (backend/)
El motor de generacion vive en un microservicio independiente escrito en Python 3.12 + FastAPI, desplegado en Google Cloud Run europe-west1 con escalado a cero. Esta aislado del frontend por dos razones: 1) la ANTHROPIC_API_KEY solo existe en el entorno del backend y jamas se expone al navegador, 2) el microservicio puede redesplegarse sin tocar el frontend (y al reves) porque Cloud Build tiene un filtro included files: backend/** en el trigger.
Estructura real
22 archivos Python · 6.214 lineas organizados por responsabilidad:
backend/
├── app/
│ ├── main.py (283 lineas — FastAPI app, CORS, lifespan, health, OpenAPI custom)
│ ├── config.py (Settings con pydantic-settings + lru_cache)
│ ├── api/
│ │ ├── routes_generate.py (849 lineas — POST /v1/generate standard + SSE)
│ │ ├── routes_adapt.py (516 lineas — POST /v1/adapt multi-plataforma)
│ │ ├── routes_profiles.py (204 lineas — CRUD /v1/profiles)
│ │ └── routes_admin.py (339 lineas — /admin/** oculto del OpenAPI publico)
│ ├── auth/
│ │ └── middleware.py (2 niveles: ADMIN Bearer + STUDIO X-Studio-Key)
│ ├── core/
│ │ ├── engine.py (1.426 lineas — nucleo de generacion, fallback y citations)
│ │ ├── streaming.py (225 lineas — wrapper SSE con eventos tipados)
│ │ ├── search_strategy.py (281 lineas — decision de activar web_search)
│ │ ├── prompts.py (100 lineas — builder del system prompt)
│ │ └── validators.py (105 lineas — validacion topic y parametros)
│ ├── db/
│ │ ├── connection.py (SQLAlchemy async engine + init_db/close_db en lifespan)
│ │ └── repository.py (578 lineas — AdminConfigRepository, ProfileRepository, HistoryRepository)
│ └── models/
│ ├── database.py (4 tablas: admin_config, profiles, generations, adaptations)
│ └── schemas.py (812 lineas — Pydantic v2: requests, responses, enums)
├── prompts/
│ ├── seo-content-generator/SKILL.md (skill base de generacion)
│ └── adapt/SKILL.md (skill de adaptacion a redes)
├── Dockerfile (multi-stage: builder con gcc + runtime slim, usuario no-root, HEALTHCHECK)
├── cloudbuild.yaml (build → push → deploy → cleanup revisiones/imagenes antiguas)
└── requirements.txt
Autenticacion en dos niveles
El middleware en app/auth/middleware.py distingue dos identidades que llaman al microservicio:
| Nivel | Header | Secreto | Uso |
|---|---|---|---|
ADMIN | Authorization: Bearer <ADMIN_API_KEY> | studio-admin-key (Secret Manager) | Acceso total, sin rate limit, endpoints /admin/** ocultos del OpenAPI publico |
STUDIO | X-Studio-Key: <STUDIO_SECRET> | studio-secret (Secret Manager) | Llamadas del frontend Next.js como proxy firmado. Rate limits aplican |
El frontend nunca expone estos secretos: los route handlers /api/internal/** de Next.js los leen de Firebase App Hosting Secret Manager y los adjuntan en servidor antes de llamar a STUDIO_API_URL. El navegador solo ve la llamada al propio dominio.
Headers adicionales para propagar contexto sin duplicar sesiones:
X-User-Id— propaga el UID de Firebase Auth al historialX-User-Tier—free/premium, determina modelo Claude usadoX-Request-Id— trace id para correlacionar logs frontend ↔ backend
Modelo de datos (SQLAlchemy 2.0 async)
4 tablas, todas declarativas con Mapped[...] y tipado estricto. Definidas en app/models/database.py:
| Tabla | Proposito | Campos clave |
|---|---|---|
admin_config | Key-value editable en caliente desde el panel admin sin redesplegar | key, value, updated_at. Cache TTL 60s en AdminConfigRepository. Claves reales: emergency_model_override, model_free, model_premium, max_tokens_<length> |
profiles | Perfiles reutilizables con tono, audiencia, idioma, style_guide, brand_voice, forbidden_words, mandatory_elements, disclaimer, seo_preferences, content_rules (JSON) | Se mergean con los parametros del request antes de llamar a Claude |
generations | Historial completo de cada generacion con input/output, tokens, web searches, modelo y status | Consultable desde /api/internal/history del frontend y desde el panel admin |
adaptations | Registro de cada adaptacion multi-plataforma con contenido origen y variantes generadas | Se enlaza con la generacion original por generation_id |
El engine async soporta tanto SQLite (sqlite+aiosqlite:///./apisdom_studio.db en desarrollo) como PostgreSQL (postgresql+asyncpg://... en produccion) via la variable DATABASE_URL. Las tablas se crean en el lifespan de FastAPI con init_db() y la conexion se cierra limpiamente con close_db() al recibir SIGTERM de Cloud Run.
Docker multi-stage optimizado para Cloud Run
El Dockerfile usa build stage con gcc para compilar dependencias nativas + runtime stage python:3.12-slim con solo el virtualenv copiado. Usuario no-root appuser, PYTHONDONTWRITEBYTECODE=1, PYTHONUNBUFFERED=1, y un HEALTHCHECK que hace urlopen contra /health cada 30s. Arranque con uvicorn --workers 1 --timeout-keep-alive 30 (1 worker porque Cloud Run ya pone 1 contenedor por instancia con concurrency 80).
Configuracion real en Cloud Run (cloudbuild.yaml):
| Parametro | Valor |
|---|---|
| Region | europe-west1 |
| CPU | 1 + --cpu-boost (arranque rapido en cold start) |
| Memoria | 512 MiB |
| Instancias min/max | 0 / 5 |
| Concurrencia por instancia | 80 |
| Timeout de request | 300s (generaciones largas con stream SSE) |
| Puerto | 8000 |
| CORS | Solo https://studio.apisdom.com (no comodin) |
| Log level | warning |
| Secretos inyectados | ANTHROPIC_API_KEY, ADMIN_API_KEY, STUDIO_SECRET desde Secret Manager |
Pipeline de despliegue automatico (trigger en push a main con filtro backend/**):
- Build de la imagen Docker
- Push a Artifact Registry
europe-west1-docker.pkg.dev/$PROJECT_ID/studio/api - Deploy a Cloud Run con la nueva revision
- Limpieza: borra todas las revisiones excepto las 2 mas recientes
- Limpieza: borra imagenes del registry excepto las 3 mas recientes
La limpieza automatica mantiene el coste de almacenamiento a cero sin intervencion manual — importante para un proyecto con despliegues frecuentes.
OpenAPI publico con branding custom
FastAPI genera automaticamente /docs (Swagger UI) y /redoc (ReDoc). En main.py se parchea el schema OpenAPI para inyectar:
info.x-logo— logo horizontal de APISDOM en ReDoc con enlace ahttps://studio.apisdom.com- Descripcion markdown extensa con tablas de tipos de contenido, idiomas, plataformas sociales y codigos de error
- Tags organizados (
generate,adapt,profiles) con descripciones detalladas - Rutas
/admin/**ocultas del schema publico coninclude_in_schema=False /health(shallow) y/health/deep(verifica DB + formato deANTHROPIC_API_KEY) fuera del schema publico
El favicon.ico se sirve desde backend/favicon/ como FileResponse estatico para que las pestanas del navegador muestren la marca correctamente en /docs y /redoc.
Calidad de codigo enforced
Sin excepciones en el backend:
mypy src/ --ignore-missing-imports --strict
ruff check src/ --select=E,W,F,B,C90,I,N,UP,S --ignore=E501
pytest
mypy --strict— todas las funciones publicas con anotaciones completas,Anysolo donde el SDK de Anthropic no expone tipos concretosruffcon reglas de seguridad (S), bugbear (B), complejidad McCabe (C90), naming (N), pyupgrade (UP) e imports ordenados (I)pytest+pytest-asynciopara cobertura de los flujos de generacion, adaptacion y perfiles
Verificadores automaticos de calidad
Cada cambio en el codigo del frontend pasa por una cadena de verificadores antes de poder commitear. El comando npm run verify:all los encadena en secuencia y un precommit de husky los dispara automaticamente. Definido en package.json:
"verify": "tsx scripts/verify-code-quality.ts",
"verify:types": "tsx scripts/verify-type-consistency.ts",
"verify:colors": "tsx scripts/find-hardcoded-colors.ts",
"verify:duplicates": "npx jscpd src --reporters console --min-lines 5 --min-tokens 50",
"verify:all": "npm run verify && npm run verify:types && npm run verify:colors && npm run type-check && npm run verify:duplicates",
"precommit": "npm run verify:all"
Que cubre cada verificador:
| Script | Regla principal |
|---|---|
verify-code-quality.ts | Maximo 40 lineas por funcion en .ts y 70 lineas por funcion en .tsx. Prohibe TODO, FIXME, HACK, WIP, TEMP. Exenciones explicitas para paginas legales, Footer y MobileMenu |
verify-type-consistency.ts | Tipos alineados entre core/entities y los schemas del backend |
find-hardcoded-colors.ts | Cero colores hardcodeados fuera del tema centralizado en config/ |
tsc --noEmit | TypeScript strict sobre los 458 archivos del proyecto |
jscpd | Cero duplicacion de codigo con umbral de 5 lineas / 50 tokens |
Auditorias adicionales en scripts/audit/:
verify-error-handling.ts— Detectacatch {}vacios,?? ''en adapters, promesas sin await y accesos a arrays sin chequeo de longitudverify-runtime-safety.ts— DetectaJSON.parsesin try/catch,fetchsin check deresponse.ok,localStoragesin fallback SSR
En el backend la cadena equivalente es:
mypy src/ --ignore-missing-imports --strict
ruff check src/ --select=E,W,F,B,C90,I,N,UP,S --ignore=E501
Con mypy --strict obligatorio: todas las funciones publicas llevan anotaciones de tipo completas y Any solo aparece donde el SDK de Anthropic no expone tipos concretos.
Cifras reales del proyecto
Todas las metricas estan contadas sobre el codigo real del repositorio, no estimadas.
| Metrica | Valor |
|---|---|
Archivos TypeScript/TSX del frontend (src/) | 458 |
Componentes React (presentation/components/**/*.tsx) | 169 |
Hooks React (presentation/hooks/) | 35 |
Route handlers Next.js (app/api/**/route.ts) | 53 |
Paginas bajo [locale] | 28 |
Entidades de dominio (core/entities/) | 14 |
Ports (interfaces) (core/ports/) | 13 |
Use-cases puros (core/use-cases/) | 18 |
| Idiomas soportados | 2 (es, en) |
| Tipos de contenido generables | 15 |
| Tonos disponibles | 10 |
| Niveles de audiencia | 4 |
| Longitudes de contenido | 4 (short / medium / long / extra_long) |
| Formatos de salida | 3 (html / markdown / plain_text) |
| Plataformas sociales soportadas | 7 (Facebook, Instagram, X, LinkedIn, TikTok, YouTube, Pinterest) |
| Modelos Claude en produccion | 2 (claude-sonnet-4-5-20250929 + claude-haiku-4-5-20251001) |
| Endpoints API publicos | /v1/generate · /v1/adapt · /v1/platforms · /v1/profiles/** |
| Region backend | Cloud Run europe-west1 |
| Region frontend | Firebase App Hosting us-central1 |
Sistema de creditos real
Definido en pricing.ts — precios en centimos de euro:
| Pack | Creditos | Precio | €/credito | Ahorro |
|---|---|---|---|---|
| Studio Starter | 10 | 9,00 € | 0,90 € | — |
| Studio Pro | 50 | 39,00 € | 0,78 € | 13 % |
| Studio Business | 200 | 129,00 € | 0,645 € | 28 % |
| Studio Enterprise | 1.000 | 499,00 € | 0,499 € | 45 % |
- 3 creditos gratis al registrarse (
FREE_CREDITS_ON_SIGNUP = 3) - Adaptaciones multi-plataforma: 1 credito para 1-3 redes, 2 creditos para 4-7 redes
- Coste variable por tipo + longitud: un
social_postcuesta 1 credito, unarticle_longcuesta 5, untutorialcuesta 4 — tabla completa encredit-costs.ts - Checkout con Stripe live (
pk_live_...), webhook enque procesa/api/webhook/route.tscheckout.session.completedy abona los creditos atomicamente - Portal de facturacion Stripe en
para gestionar metodo de pago y facturas/api/billing/portal/route.ts
Seguridad y cumplimiento
- Autenticacion: Firebase Auth en el cliente, Firebase Admin SDK en los route handlers server-side. El frontend jamas expone la clave del backend — actua como proxy firmado hacia
STUDIO_API_URL - Secrets: gestionados por Firebase App Hosting Secret Manager, declarados como
secret: ...enapphosting.yaml(STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, FIREBASE_ADMIN_PRIVATE_KEY, ZOHO_EMAIL_PASSWORD, IP_SALT, BACKEND_ADMIN_KEY...) - Sanitizacion de HTML:
isomorphic-dompurifyen todo input/output del editor - GDPR: export de datos en
y/api/user/export/route.ts, borrado de cuenta en/api/user/export-pdf/route.tscon verificacion de borrados previos, certificado GDPR propio en/api/user/delete/route.tsgdpr-certificate.ts - Newsletter con unsubscribe firmado: token HMAC con
UNSUBSCRIBE_SECRETy salt de IP (IP_SALT) para evitar enumeracion - Rate limiting + fingerprint:
@fingerprintjs/fingerprintjsen el trial gratuito para limitar abuso sin registro - Tests E2E: Playwright 1.58 con flujos criticos (login, generacion, checkout, editor, admin)
Compartir este Conocimiento
Si este proyecto aporta valor o podría inspirar a otros, considera difundirlo en tu red profesional.
