Conceptos, valor y escenarios típicos de clon — menos código.
Conceptos, valor y escenarios típicos de clon — menos código.
Preparing Ring content
Preparing Ring content
Preparing Ring content
REST para el marketplace de doble naturaleza. La lista de navegación está abierta a los visitantes; todo lo demás requiere sesión. La lista de navegación (GET /api/opportunities) es una paginación por cursor consciente del rol más enriquecimiento por espectador. La búsqueda es una ruta aparte. Like / Save / Hide / Contact son Server Actions, no recursos HTTP.
Usa las pestañas Founder / Developer para filtrar esta página. Ciclo de producto y UX de las tarjetas: Oportunidades.
Textos antiguos de la API enumeraban unos "7 tipos" fijos. Los tipos en tiempo de ejecución son la unión OpportunityType en features/opportunities/types/index.ts, limitada por ring-config.json → opportunities.enabledTypes. No documentes un conjunto cerrado de siete.
| Método | Ruta | Autenticación | Notas |
|---|---|---|---|
GET | /api/opportunities | Opcional | limit, startAfter. Los parámetros de filtro types/type, q/query, categories/category, location, priority cambian al servicio de búsqueda. Pasa viewerUserId cuando hay sesión. Devuelve { opportunities, items, lastVisible, cursor, hasMore } |
POST | /api/opportunities | Sesión | Cuerpo createOpportunityBodySchema → { opportunity } 201 |
GET | /api/opportunities/{id} | Sesión | Un anuncio |
PUT / PATCH | /api/opportunities/{id} | Sesión | Actualizar |
DELETE | /api/opportunities/{id} | Sesión | Eliminar |
GET | /api/opportunities/search | Sesión | Requiere q. Opcionales types, categories, location, limit |
POST | /api/opportunities/search | Sesión | Cuerpo JSON SearchOpportunitiesParams |
POST | /api/opportunities/{id}/sign | Sesión | Firma consultiva de una petición, una por usuario. Devuelve signatureCount, signatureTarget |
GET | /api/opportunities/my | Sesión | Lista del propietario; view= all | drafts | pending | active | archived |
POST | /api/opportunities/upload | Sesión | Adjuntos |
GET / POST | /api/mcp/v1/opportunities | MCP guard | La lista hoy no pasa viewerUserId |
Desde 2026-10-17, GET /api/opportunities sin autenticar devuelve filas públicas no confidenciales para el rol visitor. El filtro de visitante se aplica después de obtener la página, así que una página de invitado puede tener menos de limit filas. Detalle, búsqueda y cambios siguen devolviendo 401 sin sesión.
/opportunities carga GET /api/opportunities (una página de anuncios). Los filtros del panel derecho se envían como parámetros de consulta y se aplican en el servidor. Los visitantes pueden navegar sin cuenta.creator segura para el público, así que la UI nunca muestra un UUID en bruto.createOpportunity (app/_actions/opportunities.ts). El POST REST es para clientes de API y MCP; ambos llaman al mismo servicio.public | subscriber | member | confidential | entity. Las filas entity solo las pueden leer administradores, el autor y los miembros de la entidad vinculada. Las consultas de lista aplican filtros de visibilidad según el rol (buildOpportunityVisibilityFilters)./apply dedicada en alpha).Sí, solo filas públicas no confidenciales. GET /api/opportunities/{id} y la búsqueda siguen requiriendo sesión.
Sí, cuando searchOpportunities tiene el id de usuario de la sesión (o del actor MCP). Las rutas HTTP de búsqueda GET/POST requieren sesión.
createdBy?No. Sigue siendo el id de usuario. creator es una instantánea pública opcional para la UI.
La lista MCP omite viewerUserId, así que attachOpportunityFeedFields no se aplica en esa ruta.
Requisito previo: ciclo de producto, acciones de las tarjetas del feed y límites de tipos habilitados.
Depende de: etiquetas de caché, revalidatePath y Tunnel opportunity:* tras los cambios.
Mismo flujo: convertir en solicitud escribe metadata.source, que leen las tarjetas de la lista.
Mismo flujo: el vínculo con la organización es obligatorio al crear (`organizationId` / linkedEntity).
REST para el marketplace de doble naturaleza. La lista de navegación está abierta a los visitantes; todo lo demás requiere sesión. La lista de navegación (GET /api/opportunities) es una paginación por cursor consciente del rol más enriquecimiento por espectador. La búsqueda es una ruta aparte. Like / Save / Hide / Contact son Server Actions, no recursos HTTP.
Usa las pestañas Founder / Developer para filtrar esta página. Ciclo de producto y UX de las tarjetas: Oportunidades.
Textos antiguos de la API enumeraban unos "7 tipos" fijos. Los tipos en tiempo de ejecución son la unión OpportunityType en features/opportunities/types/index.ts, limitada por ring-config.json → opportunities.enabledTypes. No documentes un conjunto cerrado de siete.
| Método | Ruta | Autenticación | Notas |
|---|---|---|---|
GET | /api/opportunities | Opcional | limit, startAfter. Los parámetros de filtro types/type, q/query, categories/category, location, priority cambian al servicio de búsqueda. Pasa viewerUserId cuando hay sesión. Devuelve { opportunities, items, lastVisible, cursor, hasMore } |
POST | /api/opportunities | Sesión | Cuerpo createOpportunityBodySchema → { opportunity } 201 |
GET | /api/opportunities/{id} | Sesión | Un anuncio |
PUT / PATCH | /api/opportunities/{id} | Sesión | Actualizar |
DELETE | /api/opportunities/{id} | Sesión | Eliminar |
GET | /api/opportunities/search | Sesión | Requiere q. Opcionales types, categories, location, limit |
POST | /api/opportunities/search | Sesión | Cuerpo JSON SearchOpportunitiesParams |
POST | /api/opportunities/{id}/sign | Sesión | Firma consultiva de una petición, una por usuario. Devuelve signatureCount, signatureTarget |
GET | /api/opportunities/my | Sesión | Lista del propietario; view= all | drafts | pending | active | archived |
POST | /api/opportunities/upload | Sesión | Adjuntos |
GET / POST | /api/mcp/v1/opportunities | MCP guard | La lista hoy no pasa viewerUserId |
Desde 2026-10-17, GET /api/opportunities sin autenticar devuelve filas públicas no confidenciales para el rol visitor. El filtro de visitante se aplica después de obtener la página, así que una página de invitado puede tener menos de limit filas. Detalle, búsqueda y cambios siguen devolviendo 401 sin sesión.
/opportunities carga GET /api/opportunities (una página de anuncios). Los filtros del panel derecho se envían como parámetros de consulta y se aplican en el servidor. Los visitantes pueden navegar sin cuenta.creator segura para el público, así que la UI nunca muestra un UUID en bruto.createOpportunity (app/_actions/opportunities.ts). El POST REST es para clientes de API y MCP; ambos llaman al mismo servicio.public | subscriber | member | confidential | entity. Las filas entity solo las pueden leer administradores, el autor y los miembros de la entidad vinculada. Las consultas de lista aplican filtros de visibilidad según el rol (buildOpportunityVisibilityFilters)./apply dedicada en alpha).Sí, solo filas públicas no confidenciales. GET /api/opportunities/{id} y la búsqueda siguen requiriendo sesión.
Sí, cuando searchOpportunities tiene el id de usuario de la sesión (o del actor MCP). Las rutas HTTP de búsqueda GET/POST requieren sesión.
createdBy?No. Sigue siendo el id de usuario. creator es una instantánea pública opcional para la UI.
La lista MCP omite viewerUserId, así que attachOpportunityFeedFields no se aplica en esa ruta.
Requisito previo: ciclo de producto, acciones de las tarjetas del feed y límites de tipos habilitados.
Depende de: etiquetas de caché, revalidatePath y Tunnel opportunity:* tras los cambios.
Mismo flujo: convertir en solicitud escribe metadata.source, que leen las tarjetas de la lista.
Mismo flujo: el vínculo con la organización es obligatorio al crear (`organizationId` / linkedEntity).
REST para el marketplace de doble naturaleza. La lista de navegación está abierta a los visitantes; todo lo demás requiere sesión. La lista de navegación (GET /api/opportunities) es una paginación por cursor consciente del rol más enriquecimiento por espectador. La búsqueda es una ruta aparte. Like / Save / Hide / Contact son Server Actions, no recursos HTTP.
Usa las pestañas Founder / Developer para filtrar esta página. Ciclo de producto y UX de las tarjetas: Oportunidades.
Textos antiguos de la API enumeraban unos "7 tipos" fijos. Los tipos en tiempo de ejecución son la unión OpportunityType en features/opportunities/types/index.ts, limitada por ring-config.json → opportunities.enabledTypes. No documentes un conjunto cerrado de siete.
| Método | Ruta | Autenticación | Notas |
|---|---|---|---|
GET | /api/opportunities | Opcional | limit, startAfter. Los parámetros de filtro types/type, q/query, categories/category, location, priority cambian al servicio de búsqueda. Pasa viewerUserId cuando hay sesión. Devuelve { opportunities, items, lastVisible, cursor, hasMore } |
POST | /api/opportunities | Sesión | Cuerpo createOpportunityBodySchema → { opportunity } 201 |
GET | /api/opportunities/{id} | Sesión | Un anuncio |
PUT / PATCH | /api/opportunities/{id} | Sesión | Actualizar |
DELETE | /api/opportunities/{id} | Sesión | Eliminar |
GET | /api/opportunities/search | Sesión | Requiere q. Opcionales types, categories, location, limit |
POST | /api/opportunities/search | Sesión | Cuerpo JSON SearchOpportunitiesParams |
POST | /api/opportunities/{id}/sign | Sesión | Firma consultiva de una petición, una por usuario. Devuelve signatureCount, signatureTarget |
GET | /api/opportunities/my | Sesión | Lista del propietario; view= all | drafts | pending | active | archived |
POST | /api/opportunities/upload | Sesión | Adjuntos |
GET / POST | /api/mcp/v1/opportunities | MCP guard | La lista hoy no pasa viewerUserId |
Desde 2026-10-17, GET /api/opportunities sin autenticar devuelve filas públicas no confidenciales para el rol visitor. El filtro de visitante se aplica después de obtener la página, así que una página de invitado puede tener menos de limit filas. Detalle, búsqueda y cambios siguen devolviendo 401 sin sesión.
/opportunities carga GET /api/opportunities (una página de anuncios). Los filtros del panel derecho se envían como parámetros de consulta y se aplican en el servidor. Los visitantes pueden navegar sin cuenta.creator segura para el público, así que la UI nunca muestra un UUID en bruto.createOpportunity (app/_actions/opportunities.ts). El POST REST es para clientes de API y MCP; ambos llaman al mismo servicio.public | subscriber | member | confidential | entity. Las filas entity solo las pueden leer administradores, el autor y los miembros de la entidad vinculada. Las consultas de lista aplican filtros de visibilidad según el rol (buildOpportunityVisibilityFilters)./apply dedicada en alpha).Sí, solo filas públicas no confidenciales. GET /api/opportunities/{id} y la búsqueda siguen requiriendo sesión.
Sí, cuando searchOpportunities tiene el id de usuario de la sesión (o del actor MCP). Las rutas HTTP de búsqueda GET/POST requieren sesión.
createdBy?No. Sigue siendo el id de usuario. creator es una instantánea pública opcional para la UI.
La lista MCP omite viewerUserId, así que attachOpportunityFeedFields no se aplica en esa ruta.
Requisito previo: ciclo de producto, acciones de las tarjetas del feed y límites de tipos habilitados.
Depende de: etiquetas de caché, revalidatePath y Tunnel opportunity:* tras los cambios.
Mismo flujo: convertir en solicitud escribe metadata.source, que leen las tarjetas de la lista.
Mismo flujo: el vínculo con la organización es obligatorio al crear (`organizationId` / linkedEntity).
creator { id, name, avatar }| Acción | Módulo |
|---|---|
| Like | toggleOpportunityLike → like-service / likes |
| Save | toggleOpportunitySave → user_content_interactions |
| Hide | markOpportunityNotInterested → user_content_interactions |
| Comentario | createOpportunityComment |
| Intención de contacto | recordOpportunityContactIntent (Matcher; la UI sigue haciendo router.push a /messages?user=) |
Archivo: app/_actions/opportunity-interactions.ts.
not-inmapDbQueryRowToSerializedOpportunity() en features/opportunities/lib/opportunity-db-mapper.ts. No uses lib/converters/opportunity-converter.ts en Postgres.
El operador 'not-in' está en IDatabaseService DatabaseFilterOperator. PostgreSQL: NOT (field = ANY($n)). El adaptador de Firebase filtra después.
Las creaciones, actualizaciones, eliminaciones y cambios de estado llaman a syncOpportunityDiscovery(), que:
revalidateTag: invalida las listas de oportunidades de unstable_cache (lib/cached-data.ts)revalidatePath: /[locale]/opportunities, /[locale]/opportunities/[id], /[locale]/opportunities/myopportunity:created|updated|deleted en el canal opportunitiesNo hay un comando aparte de reindexación de búsqueda. Ver Discovery mutation sync. La navegación del cliente sigue usando useCursorFeed (24 h); revalidatePath por sí solo no limpia esa caché.
Creación en la UI: useActionState + createOpportunity de app/_actions/opportunities.ts, ver API integration.
Siguiente paso: curl con una cookie de sesión local.
creator { id, name, avatar }| Acción | Módulo |
|---|---|
| Like | toggleOpportunityLike → like-service / likes |
| Save | toggleOpportunitySave → user_content_interactions |
| Hide | markOpportunityNotInterested → user_content_interactions |
| Comentario | createOpportunityComment |
| Intención de contacto | recordOpportunityContactIntent (Matcher; la UI sigue haciendo router.push a /messages?user=) |
Archivo: app/_actions/opportunity-interactions.ts.
not-inmapDbQueryRowToSerializedOpportunity() en features/opportunities/lib/opportunity-db-mapper.ts. No uses lib/converters/opportunity-converter.ts en Postgres.
El operador 'not-in' está en IDatabaseService DatabaseFilterOperator. PostgreSQL: NOT (field = ANY($n)). El adaptador de Firebase filtra después.
Las creaciones, actualizaciones, eliminaciones y cambios de estado llaman a syncOpportunityDiscovery(), que:
revalidateTag: invalida las listas de oportunidades de unstable_cache (lib/cached-data.ts)revalidatePath: /[locale]/opportunities, /[locale]/opportunities/[id], /[locale]/opportunities/myopportunity:created|updated|deleted en el canal opportunitiesNo hay un comando aparte de reindexación de búsqueda. Ver Discovery mutation sync. La navegación del cliente sigue usando useCursorFeed (24 h); revalidatePath por sí solo no limpia esa caché.
Creación en la UI: useActionState + createOpportunity de app/_actions/opportunities.ts, ver API integration.
Siguiente paso: curl con una cookie de sesión local.
creator { id, name, avatar }| Acción | Módulo |
|---|---|
| Like | toggleOpportunityLike → like-service / likes |
| Save | toggleOpportunitySave → user_content_interactions |
| Hide | markOpportunityNotInterested → user_content_interactions |
| Comentario | createOpportunityComment |
| Intención de contacto | recordOpportunityContactIntent (Matcher; la UI sigue haciendo router.push a /messages?user=) |
Archivo: app/_actions/opportunity-interactions.ts.
not-inmapDbQueryRowToSerializedOpportunity() en features/opportunities/lib/opportunity-db-mapper.ts. No uses lib/converters/opportunity-converter.ts en Postgres.
El operador 'not-in' está en IDatabaseService DatabaseFilterOperator. PostgreSQL: NOT (field = ANY($n)). El adaptador de Firebase filtra después.
Las creaciones, actualizaciones, eliminaciones y cambios de estado llaman a syncOpportunityDiscovery(), que:
revalidateTag: invalida las listas de oportunidades de unstable_cache (lib/cached-data.ts)revalidatePath: /[locale]/opportunities, /[locale]/opportunities/[id], /[locale]/opportunities/myopportunity:created|updated|deleted en el canal opportunitiesNo hay un comando aparte de reindexación de búsqueda. Ver Discovery mutation sync. La navegación del cliente sigue usando useCursorFeed (24 h); revalidatePath por sí solo no limpia esa caché.
Creación en la UI: useActionState + createOpportunity de app/_actions/opportunities.ts, ver API integration.
Siguiente paso: curl con una cookie de sesión local.